From 012343dec5599418b77512733fc5b8db6bc14c4c Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 16:03:43 -0700 Subject: [PATCH 001/151] Add initial array_api sub-namespace This is based on the function stubs from the array API test suite, and is currently based on the assumption that NumPy already follows the array API standard. Now it needs to be modified to fix it in the places where NumPy deviates (for example, different function names for inverse trigonometric functions). --- numpy/_array_api/__init__.py | 45 ++++ numpy/_array_api/constants.py | 3 + numpy/_array_api/creation_functions.py | 45 ++++ numpy/_array_api/elementwise_functions.py | 221 +++++++++++++++++++ numpy/_array_api/linear_algebra_functions.py | 91 ++++++++ numpy/_array_api/manipulation_functions.py | 29 +++ numpy/_array_api/searching_functions.py | 17 ++ numpy/_array_api/set_functions.py | 5 + numpy/_array_api/sorting_functions.py | 9 + numpy/_array_api/statistical_functions.py | 29 +++ numpy/_array_api/utility_functions.py | 9 + 11 files changed, 503 insertions(+) create mode 100644 numpy/_array_api/__init__.py create mode 100644 numpy/_array_api/constants.py create mode 100644 numpy/_array_api/creation_functions.py create mode 100644 numpy/_array_api/elementwise_functions.py create mode 100644 numpy/_array_api/linear_algebra_functions.py create mode 100644 numpy/_array_api/manipulation_functions.py create mode 100644 numpy/_array_api/searching_functions.py create mode 100644 numpy/_array_api/set_functions.py create mode 100644 numpy/_array_api/sorting_functions.py create mode 100644 numpy/_array_api/statistical_functions.py create mode 100644 numpy/_array_api/utility_functions.py diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py new file mode 100644 index 00000000000..878251e7c54 --- /dev/null +++ b/numpy/_array_api/__init__.py @@ -0,0 +1,45 @@ +__all__ = [] + +from .constants import e, inf, nan, pi + +__all__ += ['e', 'inf', 'nan', 'pi'] + +from .creation_functions import arange, empty, empty_like, eye, full, full_like, linspace, ones, ones_like, zeros, zeros_like + +__all__ += ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] + +from .elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc + +__all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] + +from .linear_algebra_functions import cross, det, diagonal, inv, norm, outer, trace, transpose + +__all__ += ['cross', 'det', 'diagonal', 'inv', 'norm', 'outer', 'trace', 'transpose'] + +# from .linear_algebra_functions import cholesky, cross, det, diagonal, dot, eig, eigvalsh, einsum, inv, lstsq, matmul, matrix_power, matrix_rank, norm, outer, pinv, qr, slogdet, solve, svd, trace, transpose +# +# __all__ += ['cholesky', 'cross', 'det', 'diagonal', 'dot', 'eig', 'eigvalsh', 'einsum', 'inv', 'lstsq', 'matmul', 'matrix_power', 'matrix_rank', 'norm', 'outer', 'pinv', 'qr', 'slogdet', 'solve', 'svd', 'trace', 'transpose'] + +from .manipulation_functions import concat, expand_dims, flip, reshape, roll, squeeze, stack + +__all__ += ['concat', 'expand_dims', 'flip', 'reshape', 'roll', 'squeeze', 'stack'] + +from .searching_functions import argmax, argmin, nonzero, where + +__all__ += ['argmax', 'argmin', 'nonzero', 'where'] + +from .set_functions import unique + +__all__ += ['unique'] + +from .sorting_functions import argsort, sort + +__all__ += ['argsort', 'sort'] + +from .statistical_functions import max, mean, min, prod, std, sum, var + +__all__ += ['max', 'mean', 'min', 'prod', 'std', 'sum', 'var'] + +from .utility_functions import all, any + +__all__ += ['all', 'any'] diff --git a/numpy/_array_api/constants.py b/numpy/_array_api/constants.py new file mode 100644 index 00000000000..00077702958 --- /dev/null +++ b/numpy/_array_api/constants.py @@ -0,0 +1,3 @@ +from .. import e, inf, nan, pi + +__all__ = ['e', 'inf', 'nan', 'pi'] diff --git a/numpy/_array_api/creation_functions.py b/numpy/_array_api/creation_functions.py new file mode 100644 index 00000000000..50b0bd25204 --- /dev/null +++ b/numpy/_array_api/creation_functions.py @@ -0,0 +1,45 @@ +def arange(start, /, *, stop=None, step=1, dtype=None): + from .. import arange + return arange(start, stop=stop, step=step, dtype=dtype) + +def empty(shape, /, *, dtype=None): + from .. import empty + return empty(shape, dtype=dtype) + +def empty_like(x, /, *, dtype=None): + from .. import empty_like + return empty_like(x, dtype=dtype) + +def eye(N, /, *, M=None, k=0, dtype=None): + from .. import eye + return eye(N, M=M, k=k, dtype=dtype) + +def full(shape, fill_value, /, *, dtype=None): + from .. import full + return full(shape, fill_value, dtype=dtype) + +def full_like(x, fill_value, /, *, dtype=None): + from .. import full_like + return full_like(x, fill_value, dtype=dtype) + +def linspace(start, stop, num, /, *, dtype=None, endpoint=True): + from .. import linspace + return linspace(start, stop, num, dtype=dtype, endpoint=endpoint) + +def ones(shape, /, *, dtype=None): + from .. import ones + return ones(shape, dtype=dtype) + +def ones_like(x, /, *, dtype=None): + from .. import ones_like + return ones_like(x, dtype=dtype) + +def zeros(shape, /, *, dtype=None): + from .. import zeros + return zeros(shape, dtype=dtype) + +def zeros_like(x, /, *, dtype=None): + from .. import zeros_like + return zeros_like(x, dtype=dtype) + +__all__ = ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] diff --git a/numpy/_array_api/elementwise_functions.py b/numpy/_array_api/elementwise_functions.py new file mode 100644 index 00000000000..3e8349d29a2 --- /dev/null +++ b/numpy/_array_api/elementwise_functions.py @@ -0,0 +1,221 @@ +def abs(x, /): + from .. import abs + return abs(x) + +def acos(x, /): + from .. import acos + return acos(x) + +def acosh(x, /): + from .. import acosh + return acosh(x) + +def add(x1, x2, /): + from .. import add + return add(x1, x2) + +def asin(x, /): + from .. import asin + return asin(x) + +def asinh(x, /): + from .. import asinh + return asinh(x) + +def atan(x, /): + from .. import atan + return atan(x) + +def atan2(x1, x2, /): + from .. import atan2 + return atan2(x1, x2) + +def atanh(x, /): + from .. import atanh + return atanh(x) + +def bitwise_and(x1, x2, /): + from .. import bitwise_and + return bitwise_and(x1, x2) + +def bitwise_left_shift(x1, x2, /): + from .. import bitwise_left_shift + return bitwise_left_shift(x1, x2) + +def bitwise_invert(x, /): + from .. import bitwise_invert + return bitwise_invert(x) + +def bitwise_or(x1, x2, /): + from .. import bitwise_or + return bitwise_or(x1, x2) + +def bitwise_right_shift(x1, x2, /): + from .. import bitwise_right_shift + return bitwise_right_shift(x1, x2) + +def bitwise_xor(x1, x2, /): + from .. import bitwise_xor + return bitwise_xor(x1, x2) + +def ceil(x, /): + from .. import ceil + return ceil(x) + +def cos(x, /): + from .. import cos + return cos(x) + +def cosh(x, /): + from .. import cosh + return cosh(x) + +def divide(x1, x2, /): + from .. import divide + return divide(x1, x2) + +def equal(x1, x2, /): + from .. import equal + return equal(x1, x2) + +def exp(x, /): + from .. import exp + return exp(x) + +def expm1(x, /): + from .. import expm1 + return expm1(x) + +def floor(x, /): + from .. import floor + return floor(x) + +def floor_divide(x1, x2, /): + from .. import floor_divide + return floor_divide(x1, x2) + +def greater(x1, x2, /): + from .. import greater + return greater(x1, x2) + +def greater_equal(x1, x2, /): + from .. import greater_equal + return greater_equal(x1, x2) + +def isfinite(x, /): + from .. import isfinite + return isfinite(x) + +def isinf(x, /): + from .. import isinf + return isinf(x) + +def isnan(x, /): + from .. import isnan + return isnan(x) + +def less(x1, x2, /): + from .. import less + return less(x1, x2) + +def less_equal(x1, x2, /): + from .. import less_equal + return less_equal(x1, x2) + +def log(x, /): + from .. import log + return log(x) + +def log1p(x, /): + from .. import log1p + return log1p(x) + +def log2(x, /): + from .. import log2 + return log2(x) + +def log10(x, /): + from .. import log10 + return log10(x) + +def logical_and(x1, x2, /): + from .. import logical_and + return logical_and(x1, x2) + +def logical_not(x, /): + from .. import logical_not + return logical_not(x) + +def logical_or(x1, x2, /): + from .. import logical_or + return logical_or(x1, x2) + +def logical_xor(x1, x2, /): + from .. import logical_xor + return logical_xor(x1, x2) + +def multiply(x1, x2, /): + from .. import multiply + return multiply(x1, x2) + +def negative(x, /): + from .. import negative + return negative(x) + +def not_equal(x1, x2, /): + from .. import not_equal + return not_equal(x1, x2) + +def positive(x, /): + from .. import positive + return positive(x) + +def pow(x1, x2, /): + from .. import pow + return pow(x1, x2) + +def remainder(x1, x2, /): + from .. import remainder + return remainder(x1, x2) + +def round(x, /): + from .. import round + return round(x) + +def sign(x, /): + from .. import sign + return sign(x) + +def sin(x, /): + from .. import sin + return sin(x) + +def sinh(x, /): + from .. import sinh + return sinh(x) + +def square(x, /): + from .. import square + return square(x) + +def sqrt(x, /): + from .. import sqrt + return sqrt(x) + +def subtract(x1, x2, /): + from .. import subtract + return subtract(x1, x2) + +def tan(x, /): + from .. import tan + return tan(x) + +def tanh(x, /): + from .. import tanh + return tanh(x) + +def trunc(x, /): + from .. import trunc + return trunc(x) + +__all__ = ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] diff --git a/numpy/_array_api/linear_algebra_functions.py b/numpy/_array_api/linear_algebra_functions.py new file mode 100644 index 00000000000..5da7ac17b6e --- /dev/null +++ b/numpy/_array_api/linear_algebra_functions.py @@ -0,0 +1,91 @@ +# def cholesky(): +# from .. import cholesky +# return cholesky() + +def cross(x1, x2, /, *, axis=-1): + from .. import cross + return cross(x1, x2, axis=axis) + +def det(x, /): + from .. import det + return det(x) + +def diagonal(x, /, *, axis1=0, axis2=1, offset=0): + from .. import diagonal + return diagonal(x, axis1=axis1, axis2=axis2, offset=offset) + +# def dot(): +# from .. import dot +# return dot() +# +# def eig(): +# from .. import eig +# return eig() +# +# def eigvalsh(): +# from .. import eigvalsh +# return eigvalsh() +# +# def einsum(): +# from .. import einsum +# return einsum() + +def inv(x): + from .. import inv + return inv(x) + +# def lstsq(): +# from .. import lstsq +# return lstsq() +# +# def matmul(): +# from .. import matmul +# return matmul() +# +# def matrix_power(): +# from .. import matrix_power +# return matrix_power() +# +# def matrix_rank(): +# from .. import matrix_rank +# return matrix_rank() + +def norm(x, /, *, axis=None, keepdims=False, ord=None): + from .. import norm + return norm(x, axis=axis, keepdims=keepdims, ord=ord) + +def outer(x1, x2, /): + from .. import outer + return outer(x1, x2) + +# def pinv(): +# from .. import pinv +# return pinv() +# +# def qr(): +# from .. import qr +# return qr() +# +# def slogdet(): +# from .. import slogdet +# return slogdet() +# +# def solve(): +# from .. import solve +# return solve() +# +# def svd(): +# from .. import svd +# return svd() + +def trace(x, /, *, axis1=0, axis2=1, offset=0): + from .. import trace + return trace(x, axis1=axis1, axis2=axis2, offset=offset) + +def transpose(x, /, *, axes=None): + from .. import transpose + return transpose(x, axes=axes) + +# __all__ = ['cholesky', 'cross', 'det', 'diagonal', 'dot', 'eig', 'eigvalsh', 'einsum', 'inv', 'lstsq', 'matmul', 'matrix_power', 'matrix_rank', 'norm', 'outer', 'pinv', 'qr', 'slogdet', 'solve', 'svd', 'trace', 'transpose'] + +__all__ = ['cross', 'det', 'diagonal', 'inv', 'norm', 'outer', 'trace', 'transpose'] diff --git a/numpy/_array_api/manipulation_functions.py b/numpy/_array_api/manipulation_functions.py new file mode 100644 index 00000000000..1934e8e4ede --- /dev/null +++ b/numpy/_array_api/manipulation_functions.py @@ -0,0 +1,29 @@ +def concat(arrays, /, *, axis=0): + from .. import concat + return concat(arrays, axis=axis) + +def expand_dims(x, axis, /): + from .. import expand_dims + return expand_dims(x, axis) + +def flip(x, /, *, axis=None): + from .. import flip + return flip(x, axis=axis) + +def reshape(x, shape, /): + from .. import reshape + return reshape(x, shape) + +def roll(x, shift, /, *, axis=None): + from .. import roll + return roll(x, shift, axis=axis) + +def squeeze(x, /, *, axis=None): + from .. import squeeze + return squeeze(x, axis=axis) + +def stack(arrays, /, *, axis=0): + from .. import stack + return stack(arrays, axis=axis) + +__all__ = ['concat', 'expand_dims', 'flip', 'reshape', 'roll', 'squeeze', 'stack'] diff --git a/numpy/_array_api/searching_functions.py b/numpy/_array_api/searching_functions.py new file mode 100644 index 00000000000..c4b6c58b565 --- /dev/null +++ b/numpy/_array_api/searching_functions.py @@ -0,0 +1,17 @@ +def argmax(x, /, *, axis=None, keepdims=False): + from .. import argmax + return argmax(x, axis=axis, keepdims=keepdims) + +def argmin(x, /, *, axis=None, keepdims=False): + from .. import argmin + return argmin(x, axis=axis, keepdims=keepdims) + +def nonzero(x, /): + from .. import nonzero + return nonzero(x) + +def where(condition, x1, x2, /): + from .. import where + return where(condition, x1, x2) + +__all__ = ['argmax', 'argmin', 'nonzero', 'where'] diff --git a/numpy/_array_api/set_functions.py b/numpy/_array_api/set_functions.py new file mode 100644 index 00000000000..f218f118741 --- /dev/null +++ b/numpy/_array_api/set_functions.py @@ -0,0 +1,5 @@ +def unique(x, /, *, return_counts=False, return_index=False, return_inverse=False, sorted=True): + from .. import unique + return unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) + +__all__ = ['unique'] diff --git a/numpy/_array_api/sorting_functions.py b/numpy/_array_api/sorting_functions.py new file mode 100644 index 00000000000..384ec08f966 --- /dev/null +++ b/numpy/_array_api/sorting_functions.py @@ -0,0 +1,9 @@ +def argsort(x, /, *, axis=-1, descending=False, stable=True): + from .. import argsort + return argsort(x, axis=axis, descending=descending, stable=stable) + +def sort(x, /, *, axis=-1, descending=False, stable=True): + from .. import sort + return sort(x, axis=axis, descending=descending, stable=stable) + +__all__ = ['argsort', 'sort'] diff --git a/numpy/_array_api/statistical_functions.py b/numpy/_array_api/statistical_functions.py new file mode 100644 index 00000000000..2cc712aea33 --- /dev/null +++ b/numpy/_array_api/statistical_functions.py @@ -0,0 +1,29 @@ +def max(x, /, *, axis=None, keepdims=False): + from .. import max + return max(x, axis=axis, keepdims=keepdims) + +def mean(x, /, *, axis=None, keepdims=False): + from .. import mean + return mean(x, axis=axis, keepdims=keepdims) + +def min(x, /, *, axis=None, keepdims=False): + from .. import min + return min(x, axis=axis, keepdims=keepdims) + +def prod(x, /, *, axis=None, keepdims=False): + from .. import prod + return prod(x, axis=axis, keepdims=keepdims) + +def std(x, /, *, axis=None, correction=0.0, keepdims=False): + from .. import std + return std(x, axis=axis, correction=correction, keepdims=keepdims) + +def sum(x, /, *, axis=None, keepdims=False): + from .. import sum + return sum(x, axis=axis, keepdims=keepdims) + +def var(x, /, *, axis=None, correction=0.0, keepdims=False): + from .. import var + return var(x, axis=axis, correction=correction, keepdims=keepdims) + +__all__ = ['max', 'mean', 'min', 'prod', 'std', 'sum', 'var'] diff --git a/numpy/_array_api/utility_functions.py b/numpy/_array_api/utility_functions.py new file mode 100644 index 00000000000..eac0d4eaad7 --- /dev/null +++ b/numpy/_array_api/utility_functions.py @@ -0,0 +1,9 @@ +def all(x, /, *, axis=None, keepdims=False): + from .. import all + return all(x, axis=axis, keepdims=keepdims) + +def any(x, /, *, axis=None, keepdims=False): + from .. import any + return any(x, axis=axis, keepdims=keepdims) + +__all__ = ['all', 'any'] From 9934cf3abcd6ba9438c340042e94f8343e3f3d13 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 16:36:11 -0700 Subject: [PATCH 002/151] Add dtypes to the _array_api namespace --- numpy/_array_api/__init__.py | 4 ++++ numpy/_array_api/dtypes.py | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 numpy/_array_api/dtypes.py diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 878251e7c54..1677224c534 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -8,6 +8,10 @@ __all__ += ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] +from .dtypes import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool + +__all__ += ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] + from .elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc __all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] diff --git a/numpy/_array_api/dtypes.py b/numpy/_array_api/dtypes.py new file mode 100644 index 00000000000..62fb3d3219c --- /dev/null +++ b/numpy/_array_api/dtypes.py @@ -0,0 +1,3 @@ +from .. import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool + +__all__ = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] From e00760ccbfdbefea1625f5407e94397f2c85e848 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 16:43:51 -0700 Subject: [PATCH 003/151] Fix array API functions that are named differently or not in the default numpy namespace --- numpy/_array_api/elementwise_functions.py | 40 ++++++++++++-------- numpy/_array_api/linear_algebra_functions.py | 9 +++-- numpy/_array_api/manipulation_functions.py | 5 ++- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/numpy/_array_api/elementwise_functions.py b/numpy/_array_api/elementwise_functions.py index 3e8349d29a2..db7ce0a0ed2 100644 --- a/numpy/_array_api/elementwise_functions.py +++ b/numpy/_array_api/elementwise_functions.py @@ -3,36 +3,43 @@ def abs(x, /): return abs(x) def acos(x, /): - from .. import acos - return acos(x) + # Note: the function name is different here + from .. import arccos + return arccos(x) def acosh(x, /): - from .. import acosh - return acosh(x) + # Note: the function name is different here + from .. import arccosh + return arccosh(x) def add(x1, x2, /): from .. import add return add(x1, x2) def asin(x, /): - from .. import asin - return asin(x) + # Note: the function name is different here + from .. import arcsin + return arcsin(x) def asinh(x, /): - from .. import asinh - return asinh(x) + # Note: the function name is different here + from .. import arcsinh + return arcsinh(x) def atan(x, /): - from .. import atan - return atan(x) + # Note: the function name is different here + from .. import arctan + return arctan(x) def atan2(x1, x2, /): - from .. import atan2 - return atan2(x1, x2) + # Note: the function name is different here + from .. import arctan2 + return arctan2(x1, x2) def atanh(x, /): - from .. import atanh - return atanh(x) + # Note: the function name is different here + from .. import arctanh + return arctanh(x) def bitwise_and(x1, x2, /): from .. import bitwise_and @@ -171,8 +178,9 @@ def positive(x, /): return positive(x) def pow(x1, x2, /): - from .. import pow - return pow(x1, x2) + # Note: the function name is different here + from .. import power + return power(x1, x2) def remainder(x1, x2, /): from .. import remainder diff --git a/numpy/_array_api/linear_algebra_functions.py b/numpy/_array_api/linear_algebra_functions.py index 5da7ac17b6e..9995e6b98fa 100644 --- a/numpy/_array_api/linear_algebra_functions.py +++ b/numpy/_array_api/linear_algebra_functions.py @@ -7,7 +7,8 @@ def cross(x1, x2, /, *, axis=-1): return cross(x1, x2, axis=axis) def det(x, /): - from .. import det + # Note: this function is being imported from a nondefault namespace + from ..linalg import det return det(x) def diagonal(x, /, *, axis1=0, axis2=1, offset=0): @@ -31,7 +32,8 @@ def diagonal(x, /, *, axis1=0, axis2=1, offset=0): # return einsum() def inv(x): - from .. import inv + # Note: this function is being imported from a nondefault namespace + from ..linalg import inv return inv(x) # def lstsq(): @@ -51,7 +53,8 @@ def inv(x): # return matrix_rank() def norm(x, /, *, axis=None, keepdims=False, ord=None): - from .. import norm + # Note: this function is being imported from a nondefault namespace + from ..linalg import norm return norm(x, axis=axis, keepdims=keepdims, ord=ord) def outer(x1, x2, /): diff --git a/numpy/_array_api/manipulation_functions.py b/numpy/_array_api/manipulation_functions.py index 1934e8e4ede..80ca3381eb4 100644 --- a/numpy/_array_api/manipulation_functions.py +++ b/numpy/_array_api/manipulation_functions.py @@ -1,6 +1,7 @@ def concat(arrays, /, *, axis=0): - from .. import concat - return concat(arrays, axis=axis) + # Note: the function name is different here + from .. import concatenate + return concatenate(arrays, axis=axis) def expand_dims(x, axis, /): from .. import expand_dims From fcff4e1d25abb173870fffdd0a0d1f63aca7fccf Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 17:15:39 -0700 Subject: [PATCH 004/151] Fix the bool name in the array API namespace --- numpy/_array_api/dtypes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/dtypes.py b/numpy/_array_api/dtypes.py index 62fb3d3219c..e94e70e9bd7 100644 --- a/numpy/_array_api/dtypes.py +++ b/numpy/_array_api/dtypes.py @@ -1,3 +1,5 @@ -from .. import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool +from .. import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64 +# Note: This name is changed +from .. import bool_ as bool __all__ = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] From f36b64848a4577188640cc146840d5652deb6bc0 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 17:25:47 -0700 Subject: [PATCH 005/151] Fix different names for some bitwise functions in the array apis --- numpy/_array_api/elementwise_functions.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/numpy/_array_api/elementwise_functions.py b/numpy/_array_api/elementwise_functions.py index db7ce0a0ed2..bacb733d981 100644 --- a/numpy/_array_api/elementwise_functions.py +++ b/numpy/_array_api/elementwise_functions.py @@ -46,20 +46,23 @@ def bitwise_and(x1, x2, /): return bitwise_and(x1, x2) def bitwise_left_shift(x1, x2, /): - from .. import bitwise_left_shift - return bitwise_left_shift(x1, x2) + # Note: the function name is different here + from .. import left_shift + return left_shift(x1, x2) def bitwise_invert(x, /): - from .. import bitwise_invert - return bitwise_invert(x) + # Note: the function name is different here + from .. import invert + return invert(x) def bitwise_or(x1, x2, /): from .. import bitwise_or return bitwise_or(x1, x2) def bitwise_right_shift(x1, x2, /): - from .. import bitwise_right_shift - return bitwise_right_shift(x1, x2) + # Note: the function name is different here + from .. import right_shift + return right_shift(x1, x2) def bitwise_xor(x1, x2, /): from .. import bitwise_xor From c8efdbb72eab78ed2fc735d3078ef8534dcc6ef7 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 17:26:04 -0700 Subject: [PATCH 006/151] Fix different behavior of norm() with axis=None in the array API namespace --- numpy/_array_api/linear_algebra_functions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/numpy/_array_api/linear_algebra_functions.py b/numpy/_array_api/linear_algebra_functions.py index 9995e6b98fa..820dfffba32 100644 --- a/numpy/_array_api/linear_algebra_functions.py +++ b/numpy/_array_api/linear_algebra_functions.py @@ -55,6 +55,9 @@ def inv(x): def norm(x, /, *, axis=None, keepdims=False, ord=None): # Note: this function is being imported from a nondefault namespace from ..linalg import norm + # Note: this is different from the default behavior + if axis == None and x.ndim > 2: + x = x.flatten() return norm(x, axis=axis, keepdims=keepdims, ord=ord) def outer(x1, x2, /): From 10427b0cb9895d9d1d55c95815d9f27732cfbeaa Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 11 Jan 2021 17:45:05 -0700 Subject: [PATCH 007/151] Correct some differing keyword arguments in the array API namespace --- numpy/_array_api/sorting_functions.py | 14 ++++++++++++-- numpy/_array_api/statistical_functions.py | 6 ++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/numpy/_array_api/sorting_functions.py b/numpy/_array_api/sorting_functions.py index 384ec08f966..b7538773779 100644 --- a/numpy/_array_api/sorting_functions.py +++ b/numpy/_array_api/sorting_functions.py @@ -1,9 +1,19 @@ def argsort(x, /, *, axis=-1, descending=False, stable=True): from .. import argsort - return argsort(x, axis=axis, descending=descending, stable=stable) + from .. import flip + # Note: this keyword argument is different, and the default is different. + kind = 'stable' if stable else 'quicksort' + res = argsort(x, axis=axis, kind=kind) + if descending: + res = flip(res, axis=axis) def sort(x, /, *, axis=-1, descending=False, stable=True): from .. import sort - return sort(x, axis=axis, descending=descending, stable=stable) + from .. import flip + # Note: this keyword argument is different, and the default is different. + kind = 'stable' if stable else 'quicksort' + res = sort(x, axis=axis, kind=kind) + if descending: + res = flip(res, axis=axis) __all__ = ['argsort', 'sort'] diff --git a/numpy/_array_api/statistical_functions.py b/numpy/_array_api/statistical_functions.py index 2cc712aea33..b9180a863f9 100644 --- a/numpy/_array_api/statistical_functions.py +++ b/numpy/_array_api/statistical_functions.py @@ -16,7 +16,8 @@ def prod(x, /, *, axis=None, keepdims=False): def std(x, /, *, axis=None, correction=0.0, keepdims=False): from .. import std - return std(x, axis=axis, correction=correction, keepdims=keepdims) + # Note: the keyword argument correction is different here + return std(x, axis=axis, ddof=correction, keepdims=keepdims) def sum(x, /, *, axis=None, keepdims=False): from .. import sum @@ -24,6 +25,7 @@ def sum(x, /, *, axis=None, keepdims=False): def var(x, /, *, axis=None, correction=0.0, keepdims=False): from .. import var - return var(x, axis=axis, correction=correction, keepdims=keepdims) + # Note: the keyword argument correction is different here + return var(x, axis=axis, ddof=correction, keepdims=keepdims) __all__ = ['max', 'mean', 'min', 'prod', 'std', 'sum', 'var'] From d9651020aa9e1f6211b920954a357ac45712938d Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 12:32:24 -0700 Subject: [PATCH 008/151] Add the device keyword to the array creation functions --- numpy/_array_api/creation_functions.py | 55 ++++++++++++++++++++------ 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/numpy/_array_api/creation_functions.py b/numpy/_array_api/creation_functions.py index 50b0bd25204..ee3466d2fbf 100644 --- a/numpy/_array_api/creation_functions.py +++ b/numpy/_array_api/creation_functions.py @@ -1,45 +1,78 @@ -def arange(start, /, *, stop=None, step=1, dtype=None): +def arange(start, /, *, stop=None, step=1, dtype=None, device=None): from .. import arange + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return arange(start, stop=stop, step=step, dtype=dtype) -def empty(shape, /, *, dtype=None): +def empty(shape, /, *, dtype=None, device=None): from .. import empty + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return empty(shape, dtype=dtype) -def empty_like(x, /, *, dtype=None): +def empty_like(x, /, *, dtype=None, device=None): from .. import empty_like + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return empty_like(x, dtype=dtype) -def eye(N, /, *, M=None, k=0, dtype=None): +def eye(N, /, *, M=None, k=0, dtype=None, device=None): from .. import eye + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return eye(N, M=M, k=k, dtype=dtype) -def full(shape, fill_value, /, *, dtype=None): +def full(shape, fill_value, /, *, dtype=None, device=None): from .. import full + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return full(shape, fill_value, dtype=dtype) -def full_like(x, fill_value, /, *, dtype=None): +def full_like(x, fill_value, /, *, dtype=None, device=None): from .. import full_like + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return full_like(x, fill_value, dtype=dtype) -def linspace(start, stop, num, /, *, dtype=None, endpoint=True): +def linspace(start, stop, num, /, *, dtype=None, device=None, endpoint=True): from .. import linspace + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return linspace(start, stop, num, dtype=dtype, endpoint=endpoint) -def ones(shape, /, *, dtype=None): +def ones(shape, /, *, dtype=None, device=None): from .. import ones + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return ones(shape, dtype=dtype) -def ones_like(x, /, *, dtype=None): +def ones_like(x, /, *, dtype=None, device=None): from .. import ones_like + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return ones_like(x, dtype=dtype) -def zeros(shape, /, *, dtype=None): +def zeros(shape, /, *, dtype=None, device=None): from .. import zeros + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return zeros(shape, dtype=dtype) -def zeros_like(x, /, *, dtype=None): +def zeros_like(x, /, *, dtype=None, device=None): from .. import zeros_like + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") return zeros_like(x, dtype=dtype) __all__ = ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] From 9578636259f86267c2253f4af2510ce1eeaf084c Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 12:34:59 -0700 Subject: [PATCH 009/151] Make the array_api submodules private, and remove __all__ from individual files The specific submodule organization is an implementation detail and should not be used. Only the top-level numpy._array_api namespace should be used. --- numpy/_array_api/_constants.py | 1 + .../{creation_functions.py => _creation_functions.py} | 2 -- numpy/_array_api/{dtypes.py => _dtypes.py} | 2 -- .../{elementwise_functions.py => _elementwise_functions.py} | 2 -- ...near_algebra_functions.py => _linear_algebra_functions.py} | 4 ---- .../{manipulation_functions.py => _manipulation_functions.py} | 2 -- .../{searching_functions.py => _searching_functions.py} | 2 -- numpy/_array_api/{set_functions.py => _set_functions.py} | 2 -- .../{sorting_functions.py => _sorting_functions.py} | 2 -- .../{statistical_functions.py => _statistical_functions.py} | 2 -- .../{utility_functions.py => _utility_functions.py} | 2 -- numpy/_array_api/constants.py | 3 --- 12 files changed, 1 insertion(+), 25 deletions(-) create mode 100644 numpy/_array_api/_constants.py rename numpy/_array_api/{creation_functions.py => _creation_functions.py} (96%) rename numpy/_array_api/{dtypes.py => _dtypes.py} (56%) rename numpy/_array_api/{elementwise_functions.py => _elementwise_functions.py} (88%) rename numpy/_array_api/{linear_algebra_functions.py => _linear_algebra_functions.py} (86%) rename numpy/_array_api/{manipulation_functions.py => _manipulation_functions.py} (89%) rename numpy/_array_api/{searching_functions.py => _searching_functions.py} (88%) rename numpy/_array_api/{set_functions.py => _set_functions.py} (91%) rename numpy/_array_api/{sorting_functions.py => _sorting_functions.py} (95%) rename numpy/_array_api/{statistical_functions.py => _statistical_functions.py} (94%) rename numpy/_array_api/{utility_functions.py => _utility_functions.py} (89%) delete mode 100644 numpy/_array_api/constants.py diff --git a/numpy/_array_api/_constants.py b/numpy/_array_api/_constants.py new file mode 100644 index 00000000000..075b8c3b964 --- /dev/null +++ b/numpy/_array_api/_constants.py @@ -0,0 +1 @@ +from .. import e, inf, nan, pi diff --git a/numpy/_array_api/creation_functions.py b/numpy/_array_api/_creation_functions.py similarity index 96% rename from numpy/_array_api/creation_functions.py rename to numpy/_array_api/_creation_functions.py index ee3466d2fbf..4e91aa443d9 100644 --- a/numpy/_array_api/creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -74,5 +74,3 @@ def zeros_like(x, /, *, dtype=None, device=None): # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return zeros_like(x, dtype=dtype) - -__all__ = ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] diff --git a/numpy/_array_api/dtypes.py b/numpy/_array_api/_dtypes.py similarity index 56% rename from numpy/_array_api/dtypes.py rename to numpy/_array_api/_dtypes.py index e94e70e9bd7..acf87fd82ff 100644 --- a/numpy/_array_api/dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -1,5 +1,3 @@ from .. import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64 # Note: This name is changed from .. import bool_ as bool - -__all__ = ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] diff --git a/numpy/_array_api/elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py similarity index 88% rename from numpy/_array_api/elementwise_functions.py rename to numpy/_array_api/_elementwise_functions.py index bacb733d981..f9052020ea1 100644 --- a/numpy/_array_api/elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -228,5 +228,3 @@ def tanh(x, /): def trunc(x, /): from .. import trunc return trunc(x) - -__all__ = ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] diff --git a/numpy/_array_api/linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py similarity index 86% rename from numpy/_array_api/linear_algebra_functions.py rename to numpy/_array_api/_linear_algebra_functions.py index 820dfffba32..10c81d12cfd 100644 --- a/numpy/_array_api/linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -91,7 +91,3 @@ def trace(x, /, *, axis1=0, axis2=1, offset=0): def transpose(x, /, *, axes=None): from .. import transpose return transpose(x, axes=axes) - -# __all__ = ['cholesky', 'cross', 'det', 'diagonal', 'dot', 'eig', 'eigvalsh', 'einsum', 'inv', 'lstsq', 'matmul', 'matrix_power', 'matrix_rank', 'norm', 'outer', 'pinv', 'qr', 'slogdet', 'solve', 'svd', 'trace', 'transpose'] - -__all__ = ['cross', 'det', 'diagonal', 'inv', 'norm', 'outer', 'trace', 'transpose'] diff --git a/numpy/_array_api/manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py similarity index 89% rename from numpy/_array_api/manipulation_functions.py rename to numpy/_array_api/_manipulation_functions.py index 80ca3381eb4..19e9c1cab45 100644 --- a/numpy/_array_api/manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -26,5 +26,3 @@ def squeeze(x, /, *, axis=None): def stack(arrays, /, *, axis=0): from .. import stack return stack(arrays, axis=axis) - -__all__ = ['concat', 'expand_dims', 'flip', 'reshape', 'roll', 'squeeze', 'stack'] diff --git a/numpy/_array_api/searching_functions.py b/numpy/_array_api/_searching_functions.py similarity index 88% rename from numpy/_array_api/searching_functions.py rename to numpy/_array_api/_searching_functions.py index c4b6c58b565..c6035ca7765 100644 --- a/numpy/_array_api/searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -13,5 +13,3 @@ def nonzero(x, /): def where(condition, x1, x2, /): from .. import where return where(condition, x1, x2) - -__all__ = ['argmax', 'argmin', 'nonzero', 'where'] diff --git a/numpy/_array_api/set_functions.py b/numpy/_array_api/_set_functions.py similarity index 91% rename from numpy/_array_api/set_functions.py rename to numpy/_array_api/_set_functions.py index f218f118741..b6198765a6b 100644 --- a/numpy/_array_api/set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,5 +1,3 @@ def unique(x, /, *, return_counts=False, return_index=False, return_inverse=False, sorted=True): from .. import unique return unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) - -__all__ = ['unique'] diff --git a/numpy/_array_api/sorting_functions.py b/numpy/_array_api/_sorting_functions.py similarity index 95% rename from numpy/_array_api/sorting_functions.py rename to numpy/_array_api/_sorting_functions.py index b7538773779..98db3c7a28d 100644 --- a/numpy/_array_api/sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -15,5 +15,3 @@ def sort(x, /, *, axis=-1, descending=False, stable=True): res = sort(x, axis=axis, kind=kind) if descending: res = flip(res, axis=axis) - -__all__ = ['argsort', 'sort'] diff --git a/numpy/_array_api/statistical_functions.py b/numpy/_array_api/_statistical_functions.py similarity index 94% rename from numpy/_array_api/statistical_functions.py rename to numpy/_array_api/_statistical_functions.py index b9180a863f9..33983509577 100644 --- a/numpy/_array_api/statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -27,5 +27,3 @@ def var(x, /, *, axis=None, correction=0.0, keepdims=False): from .. import var # Note: the keyword argument correction is different here return var(x, axis=axis, ddof=correction, keepdims=keepdims) - -__all__ = ['max', 'mean', 'min', 'prod', 'std', 'sum', 'var'] diff --git a/numpy/_array_api/utility_functions.py b/numpy/_array_api/_utility_functions.py similarity index 89% rename from numpy/_array_api/utility_functions.py rename to numpy/_array_api/_utility_functions.py index eac0d4eaad7..accc43e1eb9 100644 --- a/numpy/_array_api/utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -5,5 +5,3 @@ def all(x, /, *, axis=None, keepdims=False): def any(x, /, *, axis=None, keepdims=False): from .. import any return any(x, axis=axis, keepdims=keepdims) - -__all__ = ['all', 'any'] diff --git a/numpy/_array_api/constants.py b/numpy/_array_api/constants.py deleted file mode 100644 index 00077702958..00000000000 --- a/numpy/_array_api/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -from .. import e, inf, nan, pi - -__all__ = ['e', 'inf', 'nan', 'pi'] From e521b16844efc2853c0db9014098cb3e37f6eb04 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 12:57:45 -0700 Subject: [PATCH 010/151] Add missing returns to the array API sorting functions --- numpy/_array_api/_sorting_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 98db3c7a28d..fb2f819a21f 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -6,6 +6,7 @@ def argsort(x, /, *, axis=-1, descending=False, stable=True): res = argsort(x, axis=axis, kind=kind) if descending: res = flip(res, axis=axis) + return res def sort(x, /, *, axis=-1, descending=False, stable=True): from .. import sort @@ -15,3 +16,4 @@ def sort(x, /, *, axis=-1, descending=False, stable=True): res = sort(x, axis=axis, kind=kind) if descending: res = flip(res, axis=axis) + return res From ba4e21ca150a2d8b3cc08a3e8c981f7042aacf6f Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 16:21:25 -0700 Subject: [PATCH 011/151] Fix the array_api submodule __init__.py imports --- numpy/_array_api/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 1677224c534..c5f8154d964 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -1,49 +1,49 @@ __all__ = [] -from .constants import e, inf, nan, pi +from ._constants import e, inf, nan, pi __all__ += ['e', 'inf', 'nan', 'pi'] -from .creation_functions import arange, empty, empty_like, eye, full, full_like, linspace, ones, ones_like, zeros, zeros_like +from ._creation_functions import arange, empty, empty_like, eye, full, full_like, linspace, ones, ones_like, zeros, zeros_like __all__ += ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] -from .dtypes import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool +from ._dtypes import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool __all__ += ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] -from .elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc +from ._elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc __all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] -from .linear_algebra_functions import cross, det, diagonal, inv, norm, outer, trace, transpose +from ._linear_algebra_functions import cross, det, diagonal, inv, norm, outer, trace, transpose __all__ += ['cross', 'det', 'diagonal', 'inv', 'norm', 'outer', 'trace', 'transpose'] -# from .linear_algebra_functions import cholesky, cross, det, diagonal, dot, eig, eigvalsh, einsum, inv, lstsq, matmul, matrix_power, matrix_rank, norm, outer, pinv, qr, slogdet, solve, svd, trace, transpose +# from ._linear_algebra_functions import cholesky, cross, det, diagonal, dot, eig, eigvalsh, einsum, inv, lstsq, matmul, matrix_power, matrix_rank, norm, outer, pinv, qr, slogdet, solve, svd, trace, transpose # # __all__ += ['cholesky', 'cross', 'det', 'diagonal', 'dot', 'eig', 'eigvalsh', 'einsum', 'inv', 'lstsq', 'matmul', 'matrix_power', 'matrix_rank', 'norm', 'outer', 'pinv', 'qr', 'slogdet', 'solve', 'svd', 'trace', 'transpose'] -from .manipulation_functions import concat, expand_dims, flip, reshape, roll, squeeze, stack +from ._manipulation_functions import concat, expand_dims, flip, reshape, roll, squeeze, stack __all__ += ['concat', 'expand_dims', 'flip', 'reshape', 'roll', 'squeeze', 'stack'] -from .searching_functions import argmax, argmin, nonzero, where +from ._searching_functions import argmax, argmin, nonzero, where __all__ += ['argmax', 'argmin', 'nonzero', 'where'] -from .set_functions import unique +from ._set_functions import unique __all__ += ['unique'] -from .sorting_functions import argsort, sort +from ._sorting_functions import argsort, sort __all__ += ['argsort', 'sort'] -from .statistical_functions import max, mean, min, prod, std, sum, var +from ._statistical_functions import max, mean, min, prod, std, sum, var __all__ += ['max', 'mean', 'min', 'prod', 'std', 'sum', 'var'] -from .utility_functions import all, any +from ._utility_functions import all, any __all__ += ['all', 'any'] From 4bd5d158e66e6b1ca5f1a767738f0674f0dc8095 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 16:21:46 -0700 Subject: [PATCH 012/151] Use "import numpy as np" in the array_api submodule This avoids importing everything inside the individual functions, but still is preferred over importing the functions used explicitly, as most of them clash with the wrapper function names. --- numpy/_array_api/_creation_functions.py | 35 ++-- numpy/_array_api/_elementwise_functions.py | 167 ++++++------------ numpy/_array_api/_linear_algebra_functions.py | 68 +++---- numpy/_array_api/_manipulation_functions.py | 23 +-- numpy/_array_api/_searching_functions.py | 14 +- numpy/_array_api/_set_functions.py | 5 +- numpy/_array_api/_sorting_functions.py | 14 +- numpy/_array_api/_statistical_functions.py | 23 +-- numpy/_array_api/_utility_functions.py | 8 +- 9 files changed, 131 insertions(+), 226 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 4e91aa443d9..b74eca0600a 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -1,76 +1,67 @@ +import numpy as np + def arange(start, /, *, stop=None, step=1, dtype=None, device=None): - from .. import arange if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return arange(start, stop=stop, step=step, dtype=dtype) + return np.arange(start, stop=stop, step=step, dtype=dtype) def empty(shape, /, *, dtype=None, device=None): - from .. import empty if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return empty(shape, dtype=dtype) + return np.empty(shape, dtype=dtype) def empty_like(x, /, *, dtype=None, device=None): - from .. import empty_like if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return empty_like(x, dtype=dtype) + return np.empty_like(x, dtype=dtype) def eye(N, /, *, M=None, k=0, dtype=None, device=None): - from .. import eye if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return eye(N, M=M, k=k, dtype=dtype) + return np.eye(N, M=M, k=k, dtype=dtype) def full(shape, fill_value, /, *, dtype=None, device=None): - from .. import full if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return full(shape, fill_value, dtype=dtype) + return np.full(shape, fill_value, dtype=dtype) def full_like(x, fill_value, /, *, dtype=None, device=None): - from .. import full_like if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return full_like(x, fill_value, dtype=dtype) + return np.full_like(x, fill_value, dtype=dtype) def linspace(start, stop, num, /, *, dtype=None, device=None, endpoint=True): - from .. import linspace if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return linspace(start, stop, num, dtype=dtype, endpoint=endpoint) + return np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint) def ones(shape, /, *, dtype=None, device=None): - from .. import ones if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ones(shape, dtype=dtype) + return np.ones(shape, dtype=dtype) def ones_like(x, /, *, dtype=None, device=None): - from .. import ones_like if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ones_like(x, dtype=dtype) + return np.ones_like(x, dtype=dtype) def zeros(shape, /, *, dtype=None, device=None): - from .. import zeros if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return zeros(shape, dtype=dtype) + return np.zeros(shape, dtype=dtype) def zeros_like(x, /, *, dtype=None, device=None): - from .. import zeros_like if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return zeros_like(x, dtype=dtype) + return np.zeros_like(x, dtype=dtype) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index f9052020ea1..ef820dd5b88 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,230 +1,177 @@ +import numpy as np + def abs(x, /): - from .. import abs - return abs(x) + return np.abs(x) def acos(x, /): # Note: the function name is different here - from .. import arccos - return arccos(x) + return np.arccos(x) def acosh(x, /): # Note: the function name is different here - from .. import arccosh - return arccosh(x) + return np.arccosh(x) def add(x1, x2, /): - from .. import add - return add(x1, x2) + return np.add(x1, x2) def asin(x, /): # Note: the function name is different here - from .. import arcsin - return arcsin(x) + return np.arcsin(x) def asinh(x, /): # Note: the function name is different here - from .. import arcsinh - return arcsinh(x) + return np.arcsinh(x) def atan(x, /): # Note: the function name is different here - from .. import arctan - return arctan(x) + return np.arctan(x) def atan2(x1, x2, /): # Note: the function name is different here - from .. import arctan2 - return arctan2(x1, x2) + return np.arctan2(x1, x2) def atanh(x, /): # Note: the function name is different here - from .. import arctanh - return arctanh(x) + return np.arctanh(x) def bitwise_and(x1, x2, /): - from .. import bitwise_and - return bitwise_and(x1, x2) + return np.bitwise_and(x1, x2) def bitwise_left_shift(x1, x2, /): # Note: the function name is different here - from .. import left_shift - return left_shift(x1, x2) + return np.left_shift(x1, x2) def bitwise_invert(x, /): # Note: the function name is different here - from .. import invert - return invert(x) + return np.invert(x) def bitwise_or(x1, x2, /): - from .. import bitwise_or - return bitwise_or(x1, x2) + return np.bitwise_or(x1, x2) def bitwise_right_shift(x1, x2, /): # Note: the function name is different here - from .. import right_shift - return right_shift(x1, x2) + return np.right_shift(x1, x2) def bitwise_xor(x1, x2, /): - from .. import bitwise_xor - return bitwise_xor(x1, x2) + return np.bitwise_xor(x1, x2) def ceil(x, /): - from .. import ceil - return ceil(x) + return np.ceil(x) def cos(x, /): - from .. import cos - return cos(x) + return np.cos(x) def cosh(x, /): - from .. import cosh - return cosh(x) + return np.cosh(x) def divide(x1, x2, /): - from .. import divide - return divide(x1, x2) + return np.divide(x1, x2) def equal(x1, x2, /): - from .. import equal - return equal(x1, x2) + return np.equal(x1, x2) def exp(x, /): - from .. import exp - return exp(x) + return np.exp(x) def expm1(x, /): - from .. import expm1 - return expm1(x) + return np.expm1(x) def floor(x, /): - from .. import floor - return floor(x) + return np.floor(x) def floor_divide(x1, x2, /): - from .. import floor_divide - return floor_divide(x1, x2) + return np.floor_divide(x1, x2) def greater(x1, x2, /): - from .. import greater - return greater(x1, x2) + return np.greater(x1, x2) def greater_equal(x1, x2, /): - from .. import greater_equal - return greater_equal(x1, x2) + return np.greater_equal(x1, x2) def isfinite(x, /): - from .. import isfinite - return isfinite(x) + return np.isfinite(x) def isinf(x, /): - from .. import isinf - return isinf(x) + return np.isinf(x) def isnan(x, /): - from .. import isnan - return isnan(x) + return np.isnan(x) def less(x1, x2, /): - from .. import less - return less(x1, x2) + return np.less(x1, x2) def less_equal(x1, x2, /): - from .. import less_equal - return less_equal(x1, x2) + return np.less_equal(x1, x2) def log(x, /): - from .. import log - return log(x) + return np.log(x) def log1p(x, /): - from .. import log1p - return log1p(x) + return np.log1p(x) def log2(x, /): - from .. import log2 - return log2(x) + return np.log2(x) def log10(x, /): - from .. import log10 - return log10(x) + return np.log10(x) def logical_and(x1, x2, /): - from .. import logical_and - return logical_and(x1, x2) + return np.logical_and(x1, x2) def logical_not(x, /): - from .. import logical_not - return logical_not(x) + return np.logical_not(x) def logical_or(x1, x2, /): - from .. import logical_or - return logical_or(x1, x2) + return np.logical_or(x1, x2) def logical_xor(x1, x2, /): - from .. import logical_xor - return logical_xor(x1, x2) + return np.logical_xor(x1, x2) def multiply(x1, x2, /): - from .. import multiply - return multiply(x1, x2) + return np.multiply(x1, x2) def negative(x, /): - from .. import negative - return negative(x) + return np.negative(x) def not_equal(x1, x2, /): - from .. import not_equal - return not_equal(x1, x2) + return np.not_equal(x1, x2) def positive(x, /): - from .. import positive - return positive(x) + return np.positive(x) def pow(x1, x2, /): # Note: the function name is different here - from .. import power - return power(x1, x2) + return np.power(x1, x2) def remainder(x1, x2, /): - from .. import remainder - return remainder(x1, x2) + return np.remainder(x1, x2) def round(x, /): - from .. import round - return round(x) + return np.round(x) def sign(x, /): - from .. import sign - return sign(x) + return np.sign(x) def sin(x, /): - from .. import sin - return sin(x) + return np.sin(x) def sinh(x, /): - from .. import sinh - return sinh(x) + return np.sinh(x) def square(x, /): - from .. import square - return square(x) + return np.square(x) def sqrt(x, /): - from .. import sqrt - return sqrt(x) + return np.sqrt(x) def subtract(x1, x2, /): - from .. import subtract - return subtract(x1, x2) + return np.subtract(x1, x2) def tan(x, /): - from .. import tan - return tan(x) + return np.tan(x) def tanh(x, /): - from .. import tanh - return tanh(x) + return np.tanh(x) def trunc(x, /): - from .. import trunc - return trunc(x) + return np.trunc(x) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index 10c81d12cfd..ffb589c9939 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,93 +1,73 @@ +import numpy as np + # def cholesky(): -# from .. import cholesky -# return cholesky() +# return np.cholesky() def cross(x1, x2, /, *, axis=-1): - from .. import cross - return cross(x1, x2, axis=axis) + return np.cross(x1, x2, axis=axis) def det(x, /): # Note: this function is being imported from a nondefault namespace - from ..linalg import det - return det(x) + return np.det(x) def diagonal(x, /, *, axis1=0, axis2=1, offset=0): - from .. import diagonal - return diagonal(x, axis1=axis1, axis2=axis2, offset=offset) + return np.diagonal(x, axis1=axis1, axis2=axis2, offset=offset) # def dot(): -# from .. import dot -# return dot() +# return np.dot() # # def eig(): -# from .. import eig -# return eig() +# return np.eig() # # def eigvalsh(): -# from .. import eigvalsh -# return eigvalsh() +# return np.eigvalsh() # # def einsum(): -# from .. import einsum -# return einsum() +# return np.einsum() def inv(x): # Note: this function is being imported from a nondefault namespace - from ..linalg import inv - return inv(x) + return np.inv(x) # def lstsq(): -# from .. import lstsq -# return lstsq() +# return np.lstsq() # # def matmul(): -# from .. import matmul -# return matmul() +# return np.matmul() # # def matrix_power(): -# from .. import matrix_power -# return matrix_power() +# return np.matrix_power() # # def matrix_rank(): -# from .. import matrix_rank -# return matrix_rank() +# return np.matrix_rank() def norm(x, /, *, axis=None, keepdims=False, ord=None): # Note: this function is being imported from a nondefault namespace - from ..linalg import norm # Note: this is different from the default behavior if axis == None and x.ndim > 2: x = x.flatten() - return norm(x, axis=axis, keepdims=keepdims, ord=ord) + return np.norm(x, axis=axis, keepdims=keepdims, ord=ord) def outer(x1, x2, /): - from .. import outer - return outer(x1, x2) + return np.outer(x1, x2) # def pinv(): -# from .. import pinv -# return pinv() +# return np.pinv() # # def qr(): -# from .. import qr -# return qr() +# return np.qr() # # def slogdet(): -# from .. import slogdet -# return slogdet() +# return np.slogdet() # # def solve(): -# from .. import solve -# return solve() +# return np.solve() # # def svd(): -# from .. import svd -# return svd() +# return np.svd() def trace(x, /, *, axis1=0, axis2=1, offset=0): - from .. import trace - return trace(x, axis1=axis1, axis2=axis2, offset=offset) + return np.trace(x, axis1=axis1, axis2=axis2, offset=offset) def transpose(x, /, *, axes=None): - from .. import transpose - return transpose(x, axes=axes) + return np.transpose(x, axes=axes) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 19e9c1cab45..262c712f80e 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,28 +1,23 @@ +import numpy as np + def concat(arrays, /, *, axis=0): # Note: the function name is different here - from .. import concatenate - return concatenate(arrays, axis=axis) + return np.concatenate(arrays, axis=axis) def expand_dims(x, axis, /): - from .. import expand_dims - return expand_dims(x, axis) + return np.expand_dims(x, axis) def flip(x, /, *, axis=None): - from .. import flip - return flip(x, axis=axis) + return np.flip(x, axis=axis) def reshape(x, shape, /): - from .. import reshape - return reshape(x, shape) + return np.reshape(x, shape) def roll(x, shift, /, *, axis=None): - from .. import roll - return roll(x, shift, axis=axis) + return np.roll(x, shift, axis=axis) def squeeze(x, /, *, axis=None): - from .. import squeeze - return squeeze(x, axis=axis) + return np.squeeze(x, axis=axis) def stack(arrays, /, *, axis=0): - from .. import stack - return stack(arrays, axis=axis) + return np.stack(arrays, axis=axis) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index c6035ca7765..62763eaca0a 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,15 +1,13 @@ +import numpy as np + def argmax(x, /, *, axis=None, keepdims=False): - from .. import argmax - return argmax(x, axis=axis, keepdims=keepdims) + return np.argmax(x, axis=axis, keepdims=keepdims) def argmin(x, /, *, axis=None, keepdims=False): - from .. import argmin - return argmin(x, axis=axis, keepdims=keepdims) + return np.argmin(x, axis=axis, keepdims=keepdims) def nonzero(x, /): - from .. import nonzero - return nonzero(x) + return np.nonzero(x) def where(condition, x1, x2, /): - from .. import where - return where(condition, x1, x2) + return np.where(condition, x1, x2) diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index b6198765a6b..7603b6b30b0 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,3 +1,4 @@ +import numpy as np + def unique(x, /, *, return_counts=False, return_index=False, return_inverse=False, sorted=True): - from .. import unique - return unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) + return np.unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index fb2f819a21f..6477029b94a 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -1,19 +1,17 @@ +import numpy as np + def argsort(x, /, *, axis=-1, descending=False, stable=True): - from .. import argsort - from .. import flip # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = argsort(x, axis=axis, kind=kind) + res = np.argsort(x, axis=axis, kind=kind) if descending: - res = flip(res, axis=axis) + res = np.flip(res, axis=axis) return res def sort(x, /, *, axis=-1, descending=False, stable=True): - from .. import sort - from .. import flip # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = sort(x, axis=axis, kind=kind) + res = np.sort(x, axis=axis, kind=kind) if descending: - res = flip(res, axis=axis) + res = np.flip(res, axis=axis) return res diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 33983509577..833c47f6602 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -1,29 +1,24 @@ +import numpy as np + def max(x, /, *, axis=None, keepdims=False): - from .. import max - return max(x, axis=axis, keepdims=keepdims) + return np.max(x, axis=axis, keepdims=keepdims) def mean(x, /, *, axis=None, keepdims=False): - from .. import mean - return mean(x, axis=axis, keepdims=keepdims) + return np.mean(x, axis=axis, keepdims=keepdims) def min(x, /, *, axis=None, keepdims=False): - from .. import min - return min(x, axis=axis, keepdims=keepdims) + return np.min(x, axis=axis, keepdims=keepdims) def prod(x, /, *, axis=None, keepdims=False): - from .. import prod - return prod(x, axis=axis, keepdims=keepdims) + return np.prod(x, axis=axis, keepdims=keepdims) def std(x, /, *, axis=None, correction=0.0, keepdims=False): - from .. import std # Note: the keyword argument correction is different here - return std(x, axis=axis, ddof=correction, keepdims=keepdims) + return np.std(x, axis=axis, ddof=correction, keepdims=keepdims) def sum(x, /, *, axis=None, keepdims=False): - from .. import sum - return sum(x, axis=axis, keepdims=keepdims) + return np.sum(x, axis=axis, keepdims=keepdims) def var(x, /, *, axis=None, correction=0.0, keepdims=False): - from .. import var # Note: the keyword argument correction is different here - return var(x, axis=axis, ddof=correction, keepdims=keepdims) + return np.var(x, axis=axis, ddof=correction, keepdims=keepdims) diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index accc43e1eb9..0bbdef41203 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -1,7 +1,7 @@ +import numpy as np + def all(x, /, *, axis=None, keepdims=False): - from .. import all - return all(x, axis=axis, keepdims=keepdims) + return np.all(x, axis=axis, keepdims=keepdims) def any(x, /, *, axis=None, keepdims=False): - from .. import any - return any(x, axis=axis, keepdims=keepdims) + return np.any(x, axis=axis, keepdims=keepdims) From a78d20a279b3f081367109338c78ab20e08c642c Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 16:30:31 -0700 Subject: [PATCH 013/151] Fix array API functions that need to use np.linalg --- numpy/_array_api/_linear_algebra_functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index ffb589c9939..920a86d9b8a 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -8,7 +8,7 @@ def cross(x1, x2, /, *, axis=-1): def det(x, /): # Note: this function is being imported from a nondefault namespace - return np.det(x) + return np.linalg.det(x) def diagonal(x, /, *, axis1=0, axis2=1, offset=0): return np.diagonal(x, axis1=axis1, axis2=axis2, offset=offset) @@ -27,7 +27,7 @@ def diagonal(x, /, *, axis1=0, axis2=1, offset=0): def inv(x): # Note: this function is being imported from a nondefault namespace - return np.inv(x) + return np.linalg.inv(x) # def lstsq(): # return np.lstsq() @@ -46,7 +46,7 @@ def norm(x, /, *, axis=None, keepdims=False, ord=None): # Note: this is different from the default behavior if axis == None and x.ndim > 2: x = x.flatten() - return np.norm(x, axis=axis, keepdims=keepdims, ord=ord) + return np.linalg.norm(x, axis=axis, keepdims=keepdims, ord=ord) def outer(x1, x2, /): return np.outer(x1, x2) From 00dda8df893d2df8730e0977178f1a116ec9cf91 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 12 Jan 2021 16:36:41 -0700 Subject: [PATCH 014/151] Add basic docstrings to the array API wrapper functions The docstrings just point back to the functions they wrap for now. More thought may need to be put into this for the future. Most functions can actually perhaps inherit the docstring of the function they wrap directly, but there are some functions that have differences (e.g., different names, different keyword arguments, fewer keyword arguments, etc.). There's also the question of how to handle cross-references/see alsos that point to functions not in the API spec and behavior shown in docstring examples that isn't required in the spec. --- numpy/_array_api/_creation_functions.py | 55 ++++ numpy/_array_api/_elementwise_functions.py | 275 ++++++++++++++++++ numpy/_array_api/_linear_algebra_functions.py | 112 ++++++- numpy/_array_api/_manipulation_functions.py | 35 +++ numpy/_array_api/_searching_functions.py | 20 ++ numpy/_array_api/_set_functions.py | 5 + numpy/_array_api/_sorting_functions.py | 10 + numpy/_array_api/_utility_functions.py | 10 + 8 files changed, 521 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index b74eca0600a..b6c0c22cc00 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -1,66 +1,121 @@ import numpy as np def arange(start, /, *, stop=None, step=1, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.arange `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.arange(start, stop=stop, step=step, dtype=dtype) def empty(shape, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.empty `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.empty(shape, dtype=dtype) def empty_like(x, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.empty_like `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.empty_like(x, dtype=dtype) def eye(N, /, *, M=None, k=0, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.eye `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.eye(N, M=M, k=k, dtype=dtype) def full(shape, fill_value, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.full `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.full(shape, fill_value, dtype=dtype) def full_like(x, fill_value, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.full_like `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.full_like(x, fill_value, dtype=dtype) def linspace(start, stop, num, /, *, dtype=None, device=None, endpoint=True): + """ + Array API compatible wrapper for :py:func:`np.linspace `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint) def ones(shape, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.ones `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.ones(shape, dtype=dtype) def ones_like(x, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.ones_like `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.ones_like(x, dtype=dtype) def zeros(shape, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.zeros `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") return np.zeros(shape, dtype=dtype) def zeros_like(x, /, *, dtype=None, device=None): + """ + Array API compatible wrapper for :py:func:`np.zeros_like `. + + See its docstring for more information. + """ if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index ef820dd5b88..7ec01b2e143 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,177 +1,452 @@ import numpy as np def abs(x, /): + """ + Array API compatible wrapper for :py:func:`np.abs `. + + See its docstring for more information. + """ return np.abs(x) def acos(x, /): + """ + Array API compatible wrapper for :py:func:`np.arccos `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arccos(x) def acosh(x, /): + """ + Array API compatible wrapper for :py:func:`np.arccosh `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arccosh(x) def add(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.add `. + + See its docstring for more information. + """ return np.add(x1, x2) def asin(x, /): + """ + Array API compatible wrapper for :py:func:`np.arcsin `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arcsin(x) def asinh(x, /): + """ + Array API compatible wrapper for :py:func:`np.arcsinh `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arcsinh(x) def atan(x, /): + """ + Array API compatible wrapper for :py:func:`np.arctan `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arctan(x) def atan2(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.arctan2 `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arctan2(x1, x2) def atanh(x, /): + """ + Array API compatible wrapper for :py:func:`np.arctanh `. + + See its docstring for more information. + """ # Note: the function name is different here return np.arctanh(x) def bitwise_and(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.bitwise_and `. + + See its docstring for more information. + """ return np.bitwise_and(x1, x2) def bitwise_left_shift(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.left_shift `. + + See its docstring for more information. + """ # Note: the function name is different here return np.left_shift(x1, x2) def bitwise_invert(x, /): + """ + Array API compatible wrapper for :py:func:`np.invert `. + + See its docstring for more information. + """ # Note: the function name is different here return np.invert(x) def bitwise_or(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.bitwise_or `. + + See its docstring for more information. + """ return np.bitwise_or(x1, x2) def bitwise_right_shift(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.right_shift `. + + See its docstring for more information. + """ # Note: the function name is different here return np.right_shift(x1, x2) def bitwise_xor(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.bitwise_xor `. + + See its docstring for more information. + """ return np.bitwise_xor(x1, x2) def ceil(x, /): + """ + Array API compatible wrapper for :py:func:`np.ceil `. + + See its docstring for more information. + """ return np.ceil(x) def cos(x, /): + """ + Array API compatible wrapper for :py:func:`np.cos `. + + See its docstring for more information. + """ return np.cos(x) def cosh(x, /): + """ + Array API compatible wrapper for :py:func:`np.cosh `. + + See its docstring for more information. + """ return np.cosh(x) def divide(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.divide `. + + See its docstring for more information. + """ return np.divide(x1, x2) def equal(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.equal `. + + See its docstring for more information. + """ return np.equal(x1, x2) def exp(x, /): + """ + Array API compatible wrapper for :py:func:`np.exp `. + + See its docstring for more information. + """ return np.exp(x) def expm1(x, /): + """ + Array API compatible wrapper for :py:func:`np.expm1 `. + + See its docstring for more information. + """ return np.expm1(x) def floor(x, /): + """ + Array API compatible wrapper for :py:func:`np.floor `. + + See its docstring for more information. + """ return np.floor(x) def floor_divide(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.floor_divide `. + + See its docstring for more information. + """ return np.floor_divide(x1, x2) def greater(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.greater `. + + See its docstring for more information. + """ return np.greater(x1, x2) def greater_equal(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.greater_equal `. + + See its docstring for more information. + """ return np.greater_equal(x1, x2) def isfinite(x, /): + """ + Array API compatible wrapper for :py:func:`np.isfinite `. + + See its docstring for more information. + """ return np.isfinite(x) def isinf(x, /): + """ + Array API compatible wrapper for :py:func:`np.isinf `. + + See its docstring for more information. + """ return np.isinf(x) def isnan(x, /): + """ + Array API compatible wrapper for :py:func:`np.isnan `. + + See its docstring for more information. + """ return np.isnan(x) def less(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.less `. + + See its docstring for more information. + """ return np.less(x1, x2) def less_equal(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.less_equal `. + + See its docstring for more information. + """ return np.less_equal(x1, x2) def log(x, /): + """ + Array API compatible wrapper for :py:func:`np.log `. + + See its docstring for more information. + """ return np.log(x) def log1p(x, /): + """ + Array API compatible wrapper for :py:func:`np.log1p `. + + See its docstring for more information. + """ return np.log1p(x) def log2(x, /): + """ + Array API compatible wrapper for :py:func:`np.log2 `. + + See its docstring for more information. + """ return np.log2(x) def log10(x, /): + """ + Array API compatible wrapper for :py:func:`np.log10 `. + + See its docstring for more information. + """ return np.log10(x) def logical_and(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.logical_and `. + + See its docstring for more information. + """ return np.logical_and(x1, x2) def logical_not(x, /): + """ + Array API compatible wrapper for :py:func:`np.logical_not `. + + See its docstring for more information. + """ return np.logical_not(x) def logical_or(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.logical_or `. + + See its docstring for more information. + """ return np.logical_or(x1, x2) def logical_xor(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.logical_xor `. + + See its docstring for more information. + """ return np.logical_xor(x1, x2) def multiply(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.multiply `. + + See its docstring for more information. + """ return np.multiply(x1, x2) def negative(x, /): + """ + Array API compatible wrapper for :py:func:`np.negative `. + + See its docstring for more information. + """ return np.negative(x) def not_equal(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.not_equal `. + + See its docstring for more information. + """ return np.not_equal(x1, x2) def positive(x, /): + """ + Array API compatible wrapper for :py:func:`np.positive `. + + See its docstring for more information. + """ return np.positive(x) def pow(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.power `. + + See its docstring for more information. + """ # Note: the function name is different here return np.power(x1, x2) def remainder(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.remainder `. + + See its docstring for more information. + """ return np.remainder(x1, x2) def round(x, /): + """ + Array API compatible wrapper for :py:func:`np.round `. + + See its docstring for more information. + """ return np.round(x) def sign(x, /): + """ + Array API compatible wrapper for :py:func:`np.sign `. + + See its docstring for more information. + """ return np.sign(x) def sin(x, /): + """ + Array API compatible wrapper for :py:func:`np.sin `. + + See its docstring for more information. + """ return np.sin(x) def sinh(x, /): + """ + Array API compatible wrapper for :py:func:`np.sinh `. + + See its docstring for more information. + """ return np.sinh(x) def square(x, /): + """ + Array API compatible wrapper for :py:func:`np.square `. + + See its docstring for more information. + """ return np.square(x) def sqrt(x, /): + """ + Array API compatible wrapper for :py:func:`np.sqrt `. + + See its docstring for more information. + """ return np.sqrt(x) def subtract(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.subtract `. + + See its docstring for more information. + """ return np.subtract(x1, x2) def tan(x, /): + """ + Array API compatible wrapper for :py:func:`np.tan `. + + See its docstring for more information. + """ return np.tan(x) def tanh(x, /): + """ + Array API compatible wrapper for :py:func:`np.tanh `. + + See its docstring for more information. + """ return np.tanh(x) def trunc(x, /): + """ + Array API compatible wrapper for :py:func:`np.trunc `. + + See its docstring for more information. + """ return np.trunc(x) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index 920a86d9b8a..cfb184e8dfa 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,73 +1,183 @@ import numpy as np # def cholesky(): +# """ +# Array API compatible wrapper for :py:func:`np.cholesky `. +# +# See its docstring for more information. +# """ # return np.cholesky() def cross(x1, x2, /, *, axis=-1): + """ + Array API compatible wrapper for :py:func:`np.cross `. + + See its docstring for more information. + """ return np.cross(x1, x2, axis=axis) def det(x, /): + """ + Array API compatible wrapper for :py:func:`np.linalg.det `. + + See its docstring for more information. + """ # Note: this function is being imported from a nondefault namespace return np.linalg.det(x) def diagonal(x, /, *, axis1=0, axis2=1, offset=0): + """ + Array API compatible wrapper for :py:func:`np.diagonal `. + + See its docstring for more information. + """ return np.diagonal(x, axis1=axis1, axis2=axis2, offset=offset) # def dot(): +# """ +# Array API compatible wrapper for :py:func:`np.dot `. +# +# See its docstring for more information. +# """ # return np.dot() # # def eig(): +# """ +# Array API compatible wrapper for :py:func:`np.eig `. +# +# See its docstring for more information. +# """ # return np.eig() # # def eigvalsh(): +# """ +# Array API compatible wrapper for :py:func:`np.eigvalsh `. +# +# See its docstring for more information. +# """ # return np.eigvalsh() # # def einsum(): +# """ +# Array API compatible wrapper for :py:func:`np.einsum `. +# +# See its docstring for more information. +# """ # return np.einsum() def inv(x): + """ + Array API compatible wrapper for :py:func:`np.linalg.inv `. + + See its docstring for more information. + """ # Note: this function is being imported from a nondefault namespace return np.linalg.inv(x) # def lstsq(): +# """ +# Array API compatible wrapper for :py:func:`np.lstsq `. +# +# See its docstring for more information. +# """ # return np.lstsq() # # def matmul(): +# """ +# Array API compatible wrapper for :py:func:`np.matmul `. +# +# See its docstring for more information. +# """ # return np.matmul() # # def matrix_power(): +# """ +# Array API compatible wrapper for :py:func:`np.matrix_power `. +# +# See its docstring for more information. +# """ # return np.matrix_power() # # def matrix_rank(): +# """ +# Array API compatible wrapper for :py:func:`np.matrix_rank `. +# +# See its docstring for more information. +# """ # return np.matrix_rank() def norm(x, /, *, axis=None, keepdims=False, ord=None): - # Note: this function is being imported from a nondefault namespace + """ + Array API compatible wrapper for :py:func:`np.linalg.norm `. + + See its docstring for more information. + """ # Note: this is different from the default behavior if axis == None and x.ndim > 2: x = x.flatten() + # Note: this function is being imported from a nondefault namespace return np.linalg.norm(x, axis=axis, keepdims=keepdims, ord=ord) def outer(x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.outer `. + + See its docstring for more information. + """ return np.outer(x1, x2) # def pinv(): +# """ +# Array API compatible wrapper for :py:func:`np.pinv `. +# +# See its docstring for more information. +# """ # return np.pinv() # # def qr(): +# """ +# Array API compatible wrapper for :py:func:`np.qr `. +# +# See its docstring for more information. +# """ # return np.qr() # # def slogdet(): +# """ +# Array API compatible wrapper for :py:func:`np.slogdet `. +# +# See its docstring for more information. +# """ # return np.slogdet() # # def solve(): +# """ +# Array API compatible wrapper for :py:func:`np.solve `. +# +# See its docstring for more information. +# """ # return np.solve() # # def svd(): +# """ +# Array API compatible wrapper for :py:func:`np.svd `. +# +# See its docstring for more information. +# """ # return np.svd() def trace(x, /, *, axis1=0, axis2=1, offset=0): + """ + Array API compatible wrapper for :py:func:`np.trace `. + + See its docstring for more information. + """ return np.trace(x, axis1=axis1, axis2=axis2, offset=offset) def transpose(x, /, *, axes=None): + """ + Array API compatible wrapper for :py:func:`np.transpose `. + + See its docstring for more information. + """ return np.transpose(x, axes=axes) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 262c712f80e..834aa2f8f5d 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,23 +1,58 @@ import numpy as np def concat(arrays, /, *, axis=0): + """ + Array API compatible wrapper for :py:func:`np.concatenate `. + + See its docstring for more information. + """ # Note: the function name is different here return np.concatenate(arrays, axis=axis) def expand_dims(x, axis, /): + """ + Array API compatible wrapper for :py:func:`np.expand_dims `. + + See its docstring for more information. + """ return np.expand_dims(x, axis) def flip(x, /, *, axis=None): + """ + Array API compatible wrapper for :py:func:`np.flip `. + + See its docstring for more information. + """ return np.flip(x, axis=axis) def reshape(x, shape, /): + """ + Array API compatible wrapper for :py:func:`np.reshape `. + + See its docstring for more information. + """ return np.reshape(x, shape) def roll(x, shift, /, *, axis=None): + """ + Array API compatible wrapper for :py:func:`np.roll `. + + See its docstring for more information. + """ return np.roll(x, shift, axis=axis) def squeeze(x, /, *, axis=None): + """ + Array API compatible wrapper for :py:func:`np.squeeze `. + + See its docstring for more information. + """ return np.squeeze(x, axis=axis) def stack(arrays, /, *, axis=0): + """ + Array API compatible wrapper for :py:func:`np.stack `. + + See its docstring for more information. + """ return np.stack(arrays, axis=axis) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 62763eaca0a..4eed66c4810 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,13 +1,33 @@ import numpy as np def argmax(x, /, *, axis=None, keepdims=False): + """ + Array API compatible wrapper for :py:func:`np.argmax `. + + See its docstring for more information. + """ return np.argmax(x, axis=axis, keepdims=keepdims) def argmin(x, /, *, axis=None, keepdims=False): + """ + Array API compatible wrapper for :py:func:`np.argmin `. + + See its docstring for more information. + """ return np.argmin(x, axis=axis, keepdims=keepdims) def nonzero(x, /): + """ + Array API compatible wrapper for :py:func:`np.nonzero `. + + See its docstring for more information. + """ return np.nonzero(x) def where(condition, x1, x2, /): + """ + Array API compatible wrapper for :py:func:`np.where `. + + See its docstring for more information. + """ return np.where(condition, x1, x2) diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 7603b6b30b0..fd1438be5d4 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,4 +1,9 @@ import numpy as np def unique(x, /, *, return_counts=False, return_index=False, return_inverse=False, sorted=True): + """ + Array API compatible wrapper for :py:func:`np.unique `. + + See its docstring for more information. + """ return np.unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 6477029b94a..5ffe6c8f9f0 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -1,6 +1,11 @@ import numpy as np def argsort(x, /, *, axis=-1, descending=False, stable=True): + """ + Array API compatible wrapper for :py:func:`np.argsort `. + + See its docstring for more information. + """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' res = np.argsort(x, axis=axis, kind=kind) @@ -9,6 +14,11 @@ def argsort(x, /, *, axis=-1, descending=False, stable=True): return res def sort(x, /, *, axis=-1, descending=False, stable=True): + """ + Array API compatible wrapper for :py:func:`np.sort `. + + See its docstring for more information. + """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' res = np.sort(x, axis=axis, kind=kind) diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 0bbdef41203..19743d15cdf 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -1,7 +1,17 @@ import numpy as np def all(x, /, *, axis=None, keepdims=False): + """ + Array API compatible wrapper for :py:func:`np.all `. + + See its docstring for more information. + """ return np.all(x, axis=axis, keepdims=keepdims) def any(x, /, *, axis=None, keepdims=False): + """ + Array API compatible wrapper for :py:func:`np.any `. + + See its docstring for more information. + """ return np.any(x, axis=axis, keepdims=keepdims) From df698f80732508af50b24ecc1b4bd34c470aaba8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 13 Jan 2021 14:00:38 -0700 Subject: [PATCH 015/151] Add an explanatory docstring to _array_api/__init__.py This is mostly aimed at any potential reviewers of the module for now. --- numpy/_array_api/__init__.py | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index c5f8154d964..ad66bc565f2 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -1,3 +1,69 @@ +""" +A NumPy sub-namespace that conforms to the Python array API standard. + +This is a proof-of-concept namespace that wraps the corresponding NumPy +functions to give a conforming implementation of the Python array API standard +(https://data-apis.github.io/array-api/latest/). The standard is currently in +an RFC phase and comments on it are both welcome and encouraged. Comments +should be made either at https://github.com/data-apis/array-api or at +https://github.com/data-apis/consortium-feedback/discussions. + +This submodule will be accompanied with a NEP (not yet written) proposing its +inclusion in NumPy. + +NumPy already follows the proposed spec for the most part, so this module +serves mostly as a thin wrapper around it. However, NumPy also implements a +lot of behavior that is not included in the spec, so this serves as a +restricted subset of the API. Only those functions that are part of the spec +are included in this namespace, and all functions are given with the exact +signature given in the spec, including the use of position-only arguments, and +omitting any extra keyword arguments implemented by NumPy but not part of the +spec. Note that the array object itself is unchanged, as implementing a +restricted subclass of ndarray seems unnecessarily complex for the purposes of +this namespace, so the API of array methods and other behaviors of the array +object will include things that are not part of the spec. + +The spec is designed as a "minimal API subset" and explicitly allows libraries +to include behaviors not specified by it. But users of this module that intend +to write portable code should be aware that only those behaviors that are +listed in the spec are guaranteed to be implemented across libraries. + +A few notes about the current state of this submodule: + +- There is a test suite that tests modules against the array API standard at + https://github.com/data-apis/array-api-tests. The test suite is still a work + in progress, but the existing tests pass on this module, with a few + exceptions: + + - Device support is not yet implemented in NumPy + (https://data-apis.github.io/array-api/latest/design_topics/device_support.html). + As a result, the `device` attribute of the array object is missing, and + array creation functions that take the `device` keyword argument will fail + with NotImplementedError. + + - DLPack support (see https://github.com/data-apis/array-api/pull/106) is + not included here, as it requires a full implementation in NumPy proper + first. + + - np.argmin and np.argmax do not implement the keepdims keyword argument. + + - Some linear algebra functions in the spec are still a work in progress (to + be added soon). These will be updated once the spec is. + + - Some tests in the test suite are still not fully correct in that they test + all datatypes whereas certain functions are only defined for a subset of + datatypes. + + The test suite is yet complete, and even the tests that exist are not + guaranteed to give a comprehensive coverage of the spec. Therefore, those + reviewing this submodule should refer to the standard documents themselves. + +- All places where the implementations in this submodule are known to deviate + from their corresponding functions in NumPy are marked with "# Note" + comments. Reviewers should make note of these comments. + +""" + __all__ = [] from ._constants import e, inf, nan, pi From be1b1932f73fb5946b4867337ba2fd2d31964d11 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 20 Jan 2021 16:11:17 -0700 Subject: [PATCH 016/151] Add type annotations to the array api submodule function definitions Some stubs still need to be modified to properly pass mypy type checking. Also, 'device' is just left as a TypeVar() for now. --- numpy/_array_api/_creation_functions.py | 26 ++-- numpy/_array_api/_elementwise_functions.py | 114 +++++++++--------- numpy/_array_api/_linear_algebra_functions.py | 20 +-- numpy/_array_api/_manipulation_functions.py | 18 +-- numpy/_array_api/_searching_functions.py | 12 +- numpy/_array_api/_set_functions.py | 6 +- numpy/_array_api/_sorting_functions.py | 8 +- numpy/_array_api/_statistical_functions.py | 18 +-- numpy/_array_api/_types.py | 18 +++ numpy/_array_api/_utility_functions.py | 8 +- 10 files changed, 151 insertions(+), 97 deletions(-) create mode 100644 numpy/_array_api/_types.py diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index b6c0c22cc00..1aeaffb716d 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import Optional, Tuple, Union, array, device, dtype + import numpy as np -def arange(start, /, *, stop=None, step=1, dtype=None, device=None): +def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.arange `. @@ -11,7 +15,7 @@ def arange(start, /, *, stop=None, step=1, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.arange(start, stop=stop, step=step, dtype=dtype) -def empty(shape, /, *, dtype=None, device=None): +def empty(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.empty `. @@ -22,7 +26,7 @@ def empty(shape, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.empty(shape, dtype=dtype) -def empty_like(x, /, *, dtype=None, device=None): +def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.empty_like `. @@ -33,7 +37,7 @@ def empty_like(x, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.empty_like(x, dtype=dtype) -def eye(N, /, *, M=None, k=0, dtype=None, device=None): +def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.eye `. @@ -44,7 +48,7 @@ def eye(N, /, *, M=None, k=0, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.eye(N, M=M, k=k, dtype=dtype) -def full(shape, fill_value, /, *, dtype=None, device=None): +def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.full `. @@ -55,7 +59,7 @@ def full(shape, fill_value, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.full(shape, fill_value, dtype=dtype) -def full_like(x, fill_value, /, *, dtype=None, device=None): +def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.full_like `. @@ -66,7 +70,7 @@ def full_like(x, fill_value, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.full_like(x, fill_value, dtype=dtype) -def linspace(start, stop, num, /, *, dtype=None, device=None, endpoint=True): +def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: Optional[bool] = True) -> array: """ Array API compatible wrapper for :py:func:`np.linspace `. @@ -77,7 +81,7 @@ def linspace(start, stop, num, /, *, dtype=None, device=None, endpoint=True): raise NotImplementedError("Device support is not yet implemented") return np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint) -def ones(shape, /, *, dtype=None, device=None): +def ones(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.ones `. @@ -88,7 +92,7 @@ def ones(shape, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.ones(shape, dtype=dtype) -def ones_like(x, /, *, dtype=None, device=None): +def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.ones_like `. @@ -99,7 +103,7 @@ def ones_like(x, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.ones_like(x, dtype=dtype) -def zeros(shape, /, *, dtype=None, device=None): +def zeros(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.zeros `. @@ -110,7 +114,7 @@ def zeros(shape, /, *, dtype=None, device=None): raise NotImplementedError("Device support is not yet implemented") return np.zeros(shape, dtype=dtype) -def zeros_like(x, /, *, dtype=None, device=None): +def zeros_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.zeros_like `. diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 7ec01b2e143..9c013d8b40b 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import array + import numpy as np -def abs(x, /): +def abs(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.abs `. @@ -8,7 +12,7 @@ def abs(x, /): """ return np.abs(x) -def acos(x, /): +def acos(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arccos `. @@ -17,7 +21,7 @@ def acos(x, /): # Note: the function name is different here return np.arccos(x) -def acosh(x, /): +def acosh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arccosh `. @@ -26,7 +30,7 @@ def acosh(x, /): # Note: the function name is different here return np.arccosh(x) -def add(x1, x2, /): +def add(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.add `. @@ -34,7 +38,7 @@ def add(x1, x2, /): """ return np.add(x1, x2) -def asin(x, /): +def asin(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arcsin `. @@ -43,7 +47,7 @@ def asin(x, /): # Note: the function name is different here return np.arcsin(x) -def asinh(x, /): +def asinh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arcsinh `. @@ -52,7 +56,7 @@ def asinh(x, /): # Note: the function name is different here return np.arcsinh(x) -def atan(x, /): +def atan(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctan `. @@ -61,7 +65,7 @@ def atan(x, /): # Note: the function name is different here return np.arctan(x) -def atan2(x1, x2, /): +def atan2(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctan2 `. @@ -70,7 +74,7 @@ def atan2(x1, x2, /): # Note: the function name is different here return np.arctan2(x1, x2) -def atanh(x, /): +def atanh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctanh `. @@ -79,7 +83,7 @@ def atanh(x, /): # Note: the function name is different here return np.arctanh(x) -def bitwise_and(x1, x2, /): +def bitwise_and(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.bitwise_and `. @@ -87,7 +91,7 @@ def bitwise_and(x1, x2, /): """ return np.bitwise_and(x1, x2) -def bitwise_left_shift(x1, x2, /): +def bitwise_left_shift(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.left_shift `. @@ -96,7 +100,7 @@ def bitwise_left_shift(x1, x2, /): # Note: the function name is different here return np.left_shift(x1, x2) -def bitwise_invert(x, /): +def bitwise_invert(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.invert `. @@ -105,7 +109,7 @@ def bitwise_invert(x, /): # Note: the function name is different here return np.invert(x) -def bitwise_or(x1, x2, /): +def bitwise_or(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.bitwise_or `. @@ -113,7 +117,7 @@ def bitwise_or(x1, x2, /): """ return np.bitwise_or(x1, x2) -def bitwise_right_shift(x1, x2, /): +def bitwise_right_shift(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.right_shift `. @@ -122,7 +126,7 @@ def bitwise_right_shift(x1, x2, /): # Note: the function name is different here return np.right_shift(x1, x2) -def bitwise_xor(x1, x2, /): +def bitwise_xor(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.bitwise_xor `. @@ -130,7 +134,7 @@ def bitwise_xor(x1, x2, /): """ return np.bitwise_xor(x1, x2) -def ceil(x, /): +def ceil(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.ceil `. @@ -138,7 +142,7 @@ def ceil(x, /): """ return np.ceil(x) -def cos(x, /): +def cos(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.cos `. @@ -146,7 +150,7 @@ def cos(x, /): """ return np.cos(x) -def cosh(x, /): +def cosh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.cosh `. @@ -154,7 +158,7 @@ def cosh(x, /): """ return np.cosh(x) -def divide(x1, x2, /): +def divide(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.divide `. @@ -162,7 +166,7 @@ def divide(x1, x2, /): """ return np.divide(x1, x2) -def equal(x1, x2, /): +def equal(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.equal `. @@ -170,7 +174,7 @@ def equal(x1, x2, /): """ return np.equal(x1, x2) -def exp(x, /): +def exp(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.exp `. @@ -178,7 +182,7 @@ def exp(x, /): """ return np.exp(x) -def expm1(x, /): +def expm1(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.expm1 `. @@ -186,7 +190,7 @@ def expm1(x, /): """ return np.expm1(x) -def floor(x, /): +def floor(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.floor `. @@ -194,7 +198,7 @@ def floor(x, /): """ return np.floor(x) -def floor_divide(x1, x2, /): +def floor_divide(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.floor_divide `. @@ -202,7 +206,7 @@ def floor_divide(x1, x2, /): """ return np.floor_divide(x1, x2) -def greater(x1, x2, /): +def greater(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.greater `. @@ -210,7 +214,7 @@ def greater(x1, x2, /): """ return np.greater(x1, x2) -def greater_equal(x1, x2, /): +def greater_equal(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.greater_equal `. @@ -218,7 +222,7 @@ def greater_equal(x1, x2, /): """ return np.greater_equal(x1, x2) -def isfinite(x, /): +def isfinite(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.isfinite `. @@ -226,7 +230,7 @@ def isfinite(x, /): """ return np.isfinite(x) -def isinf(x, /): +def isinf(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.isinf `. @@ -234,7 +238,7 @@ def isinf(x, /): """ return np.isinf(x) -def isnan(x, /): +def isnan(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.isnan `. @@ -242,7 +246,7 @@ def isnan(x, /): """ return np.isnan(x) -def less(x1, x2, /): +def less(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.less `. @@ -250,7 +254,7 @@ def less(x1, x2, /): """ return np.less(x1, x2) -def less_equal(x1, x2, /): +def less_equal(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.less_equal `. @@ -258,7 +262,7 @@ def less_equal(x1, x2, /): """ return np.less_equal(x1, x2) -def log(x, /): +def log(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log `. @@ -266,7 +270,7 @@ def log(x, /): """ return np.log(x) -def log1p(x, /): +def log1p(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log1p `. @@ -274,7 +278,7 @@ def log1p(x, /): """ return np.log1p(x) -def log2(x, /): +def log2(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log2 `. @@ -282,7 +286,7 @@ def log2(x, /): """ return np.log2(x) -def log10(x, /): +def log10(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log10 `. @@ -290,7 +294,7 @@ def log10(x, /): """ return np.log10(x) -def logical_and(x1, x2, /): +def logical_and(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.logical_and `. @@ -298,7 +302,7 @@ def logical_and(x1, x2, /): """ return np.logical_and(x1, x2) -def logical_not(x, /): +def logical_not(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.logical_not `. @@ -306,7 +310,7 @@ def logical_not(x, /): """ return np.logical_not(x) -def logical_or(x1, x2, /): +def logical_or(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.logical_or `. @@ -314,7 +318,7 @@ def logical_or(x1, x2, /): """ return np.logical_or(x1, x2) -def logical_xor(x1, x2, /): +def logical_xor(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.logical_xor `. @@ -322,7 +326,7 @@ def logical_xor(x1, x2, /): """ return np.logical_xor(x1, x2) -def multiply(x1, x2, /): +def multiply(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.multiply `. @@ -330,7 +334,7 @@ def multiply(x1, x2, /): """ return np.multiply(x1, x2) -def negative(x, /): +def negative(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.negative `. @@ -338,7 +342,7 @@ def negative(x, /): """ return np.negative(x) -def not_equal(x1, x2, /): +def not_equal(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.not_equal `. @@ -346,7 +350,7 @@ def not_equal(x1, x2, /): """ return np.not_equal(x1, x2) -def positive(x, /): +def positive(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.positive `. @@ -354,7 +358,7 @@ def positive(x, /): """ return np.positive(x) -def pow(x1, x2, /): +def pow(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.power `. @@ -363,7 +367,7 @@ def pow(x1, x2, /): # Note: the function name is different here return np.power(x1, x2) -def remainder(x1, x2, /): +def remainder(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.remainder `. @@ -371,7 +375,7 @@ def remainder(x1, x2, /): """ return np.remainder(x1, x2) -def round(x, /): +def round(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.round `. @@ -379,7 +383,7 @@ def round(x, /): """ return np.round(x) -def sign(x, /): +def sign(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sign `. @@ -387,7 +391,7 @@ def sign(x, /): """ return np.sign(x) -def sin(x, /): +def sin(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sin `. @@ -395,7 +399,7 @@ def sin(x, /): """ return np.sin(x) -def sinh(x, /): +def sinh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sinh `. @@ -403,7 +407,7 @@ def sinh(x, /): """ return np.sinh(x) -def square(x, /): +def square(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.square `. @@ -411,7 +415,7 @@ def square(x, /): """ return np.square(x) -def sqrt(x, /): +def sqrt(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sqrt `. @@ -419,7 +423,7 @@ def sqrt(x, /): """ return np.sqrt(x) -def subtract(x1, x2, /): +def subtract(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.subtract `. @@ -427,7 +431,7 @@ def subtract(x1, x2, /): """ return np.subtract(x1, x2) -def tan(x, /): +def tan(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.tan `. @@ -435,7 +439,7 @@ def tan(x, /): """ return np.tan(x) -def tanh(x, /): +def tanh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.tanh `. @@ -443,7 +447,7 @@ def tanh(x, /): """ return np.tanh(x) -def trunc(x, /): +def trunc(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.trunc `. diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index cfb184e8dfa..addbaeccba1 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from ._types import Literal, Optional, Tuple, Union, array + import numpy as np # def cholesky(): @@ -8,7 +12,7 @@ # """ # return np.cholesky() -def cross(x1, x2, /, *, axis=-1): +def cross(x1: array, x2: array, /, *, axis: int = -1) -> array: """ Array API compatible wrapper for :py:func:`np.cross `. @@ -16,7 +20,7 @@ def cross(x1, x2, /, *, axis=-1): """ return np.cross(x1, x2, axis=axis) -def det(x, /): +def det(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.linalg.det `. @@ -25,7 +29,7 @@ def det(x, /): # Note: this function is being imported from a nondefault namespace return np.linalg.det(x) -def diagonal(x, /, *, axis1=0, axis2=1, offset=0): +def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> array: """ Array API compatible wrapper for :py:func:`np.diagonal `. @@ -65,7 +69,7 @@ def diagonal(x, /, *, axis1=0, axis2=1, offset=0): # """ # return np.einsum() -def inv(x): +def inv(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.linalg.inv `. @@ -106,7 +110,7 @@ def inv(x): # """ # return np.matrix_rank() -def norm(x, /, *, axis=None, keepdims=False, ord=None): +def norm(x: array, /, *, axis: Optional[Union[int, Tuple[int, int]]] = None, keepdims: bool = False, ord: Optional[Union[int, float, Literal[np.inf, -np.inf, 'fro', 'nuc']]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.linalg.norm `. @@ -118,7 +122,7 @@ def norm(x, /, *, axis=None, keepdims=False, ord=None): # Note: this function is being imported from a nondefault namespace return np.linalg.norm(x, axis=axis, keepdims=keepdims, ord=ord) -def outer(x1, x2, /): +def outer(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.outer `. @@ -166,7 +170,7 @@ def outer(x1, x2, /): # """ # return np.svd() -def trace(x, /, *, axis1=0, axis2=1, offset=0): +def trace(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> array: """ Array API compatible wrapper for :py:func:`np.trace `. @@ -174,7 +178,7 @@ def trace(x, /, *, axis1=0, axis2=1, offset=0): """ return np.trace(x, axis1=axis1, axis2=axis2, offset=offset) -def transpose(x, /, *, axes=None): +def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.transpose `. diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 834aa2f8f5d..f79ef1f9ce6 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import Optional, Tuple, Union, array + import numpy as np -def concat(arrays, /, *, axis=0): +def concat(arrays: Tuple[array], /, *, axis: Optional[int] = 0) -> array: """ Array API compatible wrapper for :py:func:`np.concatenate `. @@ -9,7 +13,7 @@ def concat(arrays, /, *, axis=0): # Note: the function name is different here return np.concatenate(arrays, axis=axis) -def expand_dims(x, axis, /): +def expand_dims(x: array, axis: int, /) -> array: """ Array API compatible wrapper for :py:func:`np.expand_dims `. @@ -17,7 +21,7 @@ def expand_dims(x, axis, /): """ return np.expand_dims(x, axis) -def flip(x, /, *, axis=None): +def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.flip `. @@ -25,7 +29,7 @@ def flip(x, /, *, axis=None): """ return np.flip(x, axis=axis) -def reshape(x, shape, /): +def reshape(x: array, shape: Tuple[int, ...], /) -> array: """ Array API compatible wrapper for :py:func:`np.reshape `. @@ -33,7 +37,7 @@ def reshape(x, shape, /): """ return np.reshape(x, shape) -def roll(x, shift, /, *, axis=None): +def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.roll `. @@ -41,7 +45,7 @@ def roll(x, shift, /, *, axis=None): """ return np.roll(x, shift, axis=axis) -def squeeze(x, /, *, axis=None): +def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.squeeze `. @@ -49,7 +53,7 @@ def squeeze(x, /, *, axis=None): """ return np.squeeze(x, axis=axis) -def stack(arrays, /, *, axis=0): +def stack(arrays: Tuple[array], /, *, axis: Optional[int] = 0) -> array: """ Array API compatible wrapper for :py:func:`np.stack `. diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 4eed66c4810..3b37167afa9 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import Tuple, array + import numpy as np -def argmax(x, /, *, axis=None, keepdims=False): +def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: """ Array API compatible wrapper for :py:func:`np.argmax `. @@ -8,7 +12,7 @@ def argmax(x, /, *, axis=None, keepdims=False): """ return np.argmax(x, axis=axis, keepdims=keepdims) -def argmin(x, /, *, axis=None, keepdims=False): +def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: """ Array API compatible wrapper for :py:func:`np.argmin `. @@ -16,7 +20,7 @@ def argmin(x, /, *, axis=None, keepdims=False): """ return np.argmin(x, axis=axis, keepdims=keepdims) -def nonzero(x, /): +def nonzero(x: array, /) -> Tuple[array, ...]: """ Array API compatible wrapper for :py:func:`np.nonzero `. @@ -24,7 +28,7 @@ def nonzero(x, /): """ return np.nonzero(x) -def where(condition, x1, x2, /): +def where(condition: array, x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.where `. diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index fd1438be5d4..80288c57d03 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import Tuple, Union, array + import numpy as np -def unique(x, /, *, return_counts=False, return_index=False, return_inverse=False, sorted=True): +def unique(x: array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False, sorted: bool = True) -> Union[array, Tuple[array, ...]]: """ Array API compatible wrapper for :py:func:`np.unique `. diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 5ffe6c8f9f0..cddfd1598ae 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import array + import numpy as np -def argsort(x, /, *, axis=-1, descending=False, stable=True): +def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> array: """ Array API compatible wrapper for :py:func:`np.argsort `. @@ -13,7 +17,7 @@ def argsort(x, /, *, axis=-1, descending=False, stable=True): res = np.flip(res, axis=axis) return res -def sort(x, /, *, axis=-1, descending=False, stable=True): +def sort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> array: """ Array API compatible wrapper for :py:func:`np.sort `. diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 833c47f6602..0200538966f 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -1,24 +1,28 @@ +from __future__ import annotations + +from ._types import Optional, Tuple, Union, array + import numpy as np -def max(x, /, *, axis=None, keepdims=False): +def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: return np.max(x, axis=axis, keepdims=keepdims) -def mean(x, /, *, axis=None, keepdims=False): +def mean(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: return np.mean(x, axis=axis, keepdims=keepdims) -def min(x, /, *, axis=None, keepdims=False): +def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: return np.min(x, axis=axis, keepdims=keepdims) -def prod(x, /, *, axis=None, keepdims=False): +def prod(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: return np.prod(x, axis=axis, keepdims=keepdims) -def std(x, /, *, axis=None, correction=0.0, keepdims=False): +def std(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here return np.std(x, axis=axis, ddof=correction, keepdims=keepdims) -def sum(x, /, *, axis=None, keepdims=False): +def sum(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: return np.sum(x, axis=axis, keepdims=keepdims) -def var(x, /, *, axis=None, correction=0.0, keepdims=False): +def var(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here return np.var(x, axis=axis, ddof=correction, keepdims=keepdims) diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py new file mode 100644 index 00000000000..e8867a29b33 --- /dev/null +++ b/numpy/_array_api/_types.py @@ -0,0 +1,18 @@ +""" +This file defines the types for type annotations. + +These names aren't part of the module namespace, but they are used in the +annotations in the function signatures. The functions in the module are only +valid for inputs that match the given type annotations. +""" + +__all__ = ['Literal', 'Optional', 'Tuple', 'Union', 'array', 'device', 'dtype'] + +from typing import Literal, Optional, Tuple, Union, TypeVar + +import numpy as np + +array = np.ndarray +device = TypeVar('device') +dtype = Literal[np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, + np.uint32, np.uint64, np.float32, np.float64] diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 19743d15cdf..69e17e0e5fb 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +from ._types import Optional, Tuple, Union, array + import numpy as np -def all(x, /, *, axis=None, keepdims=False): +def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: """ Array API compatible wrapper for :py:func:`np.all `. @@ -8,7 +12,7 @@ def all(x, /, *, axis=None, keepdims=False): """ return np.all(x, axis=axis, keepdims=keepdims) -def any(x, /, *, axis=None, keepdims=False): +def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: """ Array API compatible wrapper for :py:func:`np.any `. From 5df8ec9673a73e71554c8f53cc6edb60533c5d17 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 20 Jan 2021 17:56:37 -0700 Subject: [PATCH 017/151] Fix some incorrect type annotations in the array API submodule (see https://github.com/data-apis/array-api/pull/116) --- numpy/_array_api/_creation_functions.py | 2 +- numpy/_array_api/_manipulation_functions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 1aeaffb716d..df64ed1d662 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -70,7 +70,7 @@ def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dty raise NotImplementedError("Device support is not yet implemented") return np.full_like(x, fill_value, dtype=dtype) -def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: Optional[bool] = True) -> array: +def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: """ Array API compatible wrapper for :py:func:`np.linspace `. diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index f79ef1f9ce6..a4247f2b5c3 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -53,7 +53,7 @@ def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) """ return np.squeeze(x, axis=axis) -def stack(arrays: Tuple[array], /, *, axis: Optional[int] = 0) -> array: +def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: """ Array API compatible wrapper for :py:func:`np.stack `. From ad19f7f7dfcfe33fd4591f1be3b4d9d30887899a Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 20 Jan 2021 17:57:10 -0700 Subject: [PATCH 018/151] Use np.asarray in the array API submodule for any function that can return a scalar This is needed to pass mypy type checks for the given type annotations. --- numpy/_array_api/_linear_algebra_functions.py | 2 +- numpy/_array_api/_searching_functions.py | 6 ++++-- numpy/_array_api/_statistical_functions.py | 10 +++++----- numpy/_array_api/_utility_functions.py | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index addbaeccba1..ec67f9c0b9c 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -176,7 +176,7 @@ def trace(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> ar See its docstring for more information. """ - return np.trace(x, axis1=axis1, axis2=axis2, offset=offset) + return np.asarray(np.trace(x, axis1=axis1, axis2=axis2, offset=offset)) def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: """ diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 3b37167afa9..d5128cca979 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -10,7 +10,8 @@ def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ - return np.argmax(x, axis=axis, keepdims=keepdims) + # Note: this currently fails as np.argmax does not implement keepdims + return np.asarray(np.argmax(x, axis=axis, keepdims=keepdims)) def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: """ @@ -18,7 +19,8 @@ def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ - return np.argmin(x, axis=axis, keepdims=keepdims) + # Note: this currently fails as np.argmin does not implement keepdims + return np.asarray(np.argmin(x, axis=axis, keepdims=keepdims)) def nonzero(x: array, /) -> Tuple[array, ...]: """ diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 0200538966f..e62410d01ec 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -8,21 +8,21 @@ def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep return np.max(x, axis=axis, keepdims=keepdims) def mean(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.mean(x, axis=axis, keepdims=keepdims) + return np.asarray(np.mean(x, axis=axis, keepdims=keepdims)) def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: return np.min(x, axis=axis, keepdims=keepdims) def prod(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.prod(x, axis=axis, keepdims=keepdims) + return np.asarray(np.prod(x, axis=axis, keepdims=keepdims)) def std(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.std(x, axis=axis, ddof=correction, keepdims=keepdims) + return np.asarray(np.std(x, axis=axis, ddof=correction, keepdims=keepdims)) def sum(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.sum(x, axis=axis, keepdims=keepdims) + return np.asarray(np.sum(x, axis=axis, keepdims=keepdims)) def var(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.var(x, axis=axis, ddof=correction, keepdims=keepdims) + return np.asarray(np.var(x, axis=axis, ddof=correction, keepdims=keepdims)) diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 69e17e0e5fb..51a04dc8bbd 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -10,7 +10,7 @@ def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.all(x, axis=axis, keepdims=keepdims) + return np.asarray(np.all(x, axis=axis, keepdims=keepdims)) def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: """ @@ -18,4 +18,4 @@ def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.any(x, axis=axis, keepdims=keepdims) + return np.asarray(np.any(x, axis=axis, keepdims=keepdims)) From 1efd55efa8cac9afd12d299dcf8912a7a7ac8a68 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 20 Jan 2021 18:25:20 -0700 Subject: [PATCH 019/151] Use _implementation on all functions that have it in the array API submodule That way they only work on actual ndarray inputs, not array-like, which is more inline with the spec. --- numpy/_array_api/_creation_functions.py | 8 ++++---- numpy/_array_api/_elementwise_functions.py | 2 +- numpy/_array_api/_linear_algebra_functions.py | 10 +++++----- numpy/_array_api/_manipulation_functions.py | 12 ++++++------ numpy/_array_api/_searching_functions.py | 8 ++++---- numpy/_array_api/_set_functions.py | 2 +- numpy/_array_api/_sorting_functions.py | 4 ++-- numpy/_array_api/_statistical_functions.py | 14 +++++++------- numpy/_array_api/_utility_functions.py | 4 ++-- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index df64ed1d662..68326f2916d 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -35,7 +35,7 @@ def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.empty_like(x, dtype=dtype) + return np.empty_like._implementation(x, dtype=dtype) def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -68,7 +68,7 @@ def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dty if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.full_like(x, fill_value, dtype=dtype) + return np.full_like._implementation(x, fill_value, dtype=dtype) def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: """ @@ -101,7 +101,7 @@ def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[de if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.ones_like(x, dtype=dtype) + return np.ones_like._implementation(x, dtype=dtype) def zeros(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -123,4 +123,4 @@ def zeros_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.zeros_like(x, dtype=dtype) + return np.zeros_like._implementation(x, dtype=dtype) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 9c013d8b40b..9de4261acf7 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -381,7 +381,7 @@ def round(x: array, /) -> array: See its docstring for more information. """ - return np.round(x) + return np.round._implementation(x) def sign(x: array, /) -> array: """ diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index ec67f9c0b9c..e23800e0fe4 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -18,7 +18,7 @@ def cross(x1: array, x2: array, /, *, axis: int = -1) -> array: See its docstring for more information. """ - return np.cross(x1, x2, axis=axis) + return np.cross._implementation(x1, x2, axis=axis) def det(x: array, /) -> array: """ @@ -35,7 +35,7 @@ def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> See its docstring for more information. """ - return np.diagonal(x, axis1=axis1, axis2=axis2, offset=offset) + return np.diagonal._implementation(x, axis1=axis1, axis2=axis2, offset=offset) # def dot(): # """ @@ -128,7 +128,7 @@ def outer(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.outer(x1, x2) + return np.outer._implementation(x1, x2) # def pinv(): # """ @@ -176,7 +176,7 @@ def trace(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> ar See its docstring for more information. """ - return np.asarray(np.trace(x, axis1=axis1, axis2=axis2, offset=offset)) + return np.asarray(np.trace._implementation(x, axis1=axis1, axis2=axis2, offset=offset)) def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: """ @@ -184,4 +184,4 @@ def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: See its docstring for more information. """ - return np.transpose(x, axes=axes) + return np.transpose._implementation(x, axes=axes) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index a4247f2b5c3..e312b18c51d 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -19,7 +19,7 @@ def expand_dims(x: array, axis: int, /) -> array: See its docstring for more information. """ - return np.expand_dims(x, axis) + return np.expand_dims._implementation(x, axis) def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -27,7 +27,7 @@ def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> See its docstring for more information. """ - return np.flip(x, axis=axis) + return np.flip._implementation(x, axis=axis) def reshape(x: array, shape: Tuple[int, ...], /) -> array: """ @@ -35,7 +35,7 @@ def reshape(x: array, shape: Tuple[int, ...], /) -> array: See its docstring for more information. """ - return np.reshape(x, shape) + return np.reshape._implementation(x, shape) def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -43,7 +43,7 @@ def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Unio See its docstring for more information. """ - return np.roll(x, shift, axis=axis) + return np.roll._implementation(x, shift, axis=axis) def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -51,7 +51,7 @@ def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) See its docstring for more information. """ - return np.squeeze(x, axis=axis) + return np.squeeze._implementation(x, axis=axis) def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: """ @@ -59,4 +59,4 @@ def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: See its docstring for more information. """ - return np.stack(arrays, axis=axis) + return np.stack._implementation(arrays, axis=axis) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index d5128cca979..77e4710e57d 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -11,7 +11,7 @@ def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ # Note: this currently fails as np.argmax does not implement keepdims - return np.asarray(np.argmax(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.argmax._implementation(x, axis=axis, keepdims=keepdims)) def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: """ @@ -20,7 +20,7 @@ def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ # Note: this currently fails as np.argmin does not implement keepdims - return np.asarray(np.argmin(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.argmin._implementation(x, axis=axis, keepdims=keepdims)) def nonzero(x: array, /) -> Tuple[array, ...]: """ @@ -28,7 +28,7 @@ def nonzero(x: array, /) -> Tuple[array, ...]: See its docstring for more information. """ - return np.nonzero(x) + return np.nonzero._implementation(x) def where(condition: array, x1: array, x2: array, /) -> array: """ @@ -36,4 +36,4 @@ def where(condition: array, x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.where(condition, x1, x2) + return np.where._implementation(condition, x1, x2) diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 80288c57d03..0a75d727e61 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -10,4 +10,4 @@ def unique(x: array, /, *, return_counts: bool = False, return_index: bool = Fal See its docstring for more information. """ - return np.unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) + return np.unique._implementation(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index cddfd1598ae..17316b552e1 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -12,7 +12,7 @@ def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bo """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = np.argsort(x, axis=axis, kind=kind) + res = np.argsort._implementation(x, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) return res @@ -25,7 +25,7 @@ def sort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = np.sort(x, axis=axis, kind=kind) + res = np.sort._implementation(x, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) return res diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index e62410d01ec..79bc125dc86 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -5,24 +5,24 @@ import numpy as np def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.max(x, axis=axis, keepdims=keepdims) + return np.max._implementation(x, axis=axis, keepdims=keepdims) def mean(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.mean(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.mean._implementation(x, axis=axis, keepdims=keepdims)) def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.min(x, axis=axis, keepdims=keepdims) + return np.min._implementation(x, axis=axis, keepdims=keepdims) def prod(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.prod(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.prod._implementation(x, axis=axis, keepdims=keepdims)) def std(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.asarray(np.std(x, axis=axis, ddof=correction, keepdims=keepdims)) + return np.asarray(np.std._implementation(x, axis=axis, ddof=correction, keepdims=keepdims)) def sum(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.sum(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.sum._implementation(x, axis=axis, keepdims=keepdims)) def var(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.asarray(np.var(x, axis=axis, ddof=correction, keepdims=keepdims)) + return np.asarray(np.var._implementation(x, axis=axis, ddof=correction, keepdims=keepdims)) diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 51a04dc8bbd..7e1d6ec6e9d 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -10,7 +10,7 @@ def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.asarray(np.all(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.all._implementation(x, axis=axis, keepdims=keepdims)) def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: """ @@ -18,4 +18,4 @@ def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.asarray(np.any(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.any._implementation(x, axis=axis, keepdims=keepdims)) From affc5f0c2581a8d17825bcb7d9610e4f58560b5d Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 20 Jan 2021 18:30:45 -0700 Subject: [PATCH 020/151] Add some more notes to the array API module docstring --- numpy/_array_api/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index ad66bc565f2..6afddb26a5b 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -58,6 +58,20 @@ guaranteed to give a comprehensive coverage of the spec. Therefore, those reviewing this submodule should refer to the standard documents themselves. +- All functions include type annotations, corresponding to those given in the + spec (see _types.py for definitions of the types 'array', 'device', and + 'dtype'). These do not currently fully pass mypy due to some limitations in + mypy. + +- The array object is not modified at all. That means that functions return + np.ndarray, which has methods and attributes that aren't part of the spec. + Modifying/subclassing ndarray for the purposes of the array API namespace + was considered too complex for this initial implementation. + +- All functions that would otherwise accept array-like input have been wrapped + to only accept ndarray (with the exception of methods on the array object, + which are not modified). + - All places where the implementations in this submodule are known to deviate from their corresponding functions in NumPy are marked with "# Note" comments. Reviewers should make note of these comments. From f2ac67e236c1dbc60f66c4a4b041403a197e52f2 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 16:17:07 -0700 Subject: [PATCH 021/151] Update array_api namespace with latest changes from the spec --- numpy/_array_api/_creation_functions.py | 18 ++++++++++++- numpy/_array_api/_data_type_functions.py | 30 ++++++++++++++++++++++ numpy/_array_api/_elementwise_functions.py | 8 ++++++ numpy/_array_api/_set_functions.py | 4 +-- numpy/_array_api/_types.py | 6 ++++- 5 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 numpy/_array_api/_data_type_functions.py diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 68326f2916d..d015734ff15 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -1,9 +1,21 @@ from __future__ import annotations -from ._types import Optional, Tuple, Union, array, device, dtype +from ._types import (Optional, SupportsDLPack, SupportsBufferProtocol, Tuple, + Union, array, device, dtype) import numpy as np +def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, copy: Optional[bool] = None) -> array: + """ + Array API compatible wrapper for :py:func:`np.asarray `. + + See its docstring for more information. + """ + if device is not None: + # Note: Device support is not yet implemented on ndarray + raise NotImplementedError("Device support is not yet implemented") + return np.asarray(obj, dtype=dtype, copy=copy) + def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.arange `. @@ -48,6 +60,10 @@ def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Opti raise NotImplementedError("Device support is not yet implemented") return np.eye(N, M=M, k=k, dtype=dtype) +def from_dlpack(x: object, /) -> array: + # Note: dlpack support is not yet implemented on ndarray + raise NotImplementedError("DLPack support is not yet implemented") + def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.full `. diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py new file mode 100644 index 00000000000..18f741ebdc8 --- /dev/null +++ b/numpy/_array_api/_data_type_functions.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from ._types import Union, array, dtype +from collections.abc import Sequence + +import numpy as np + +def finfo(type: Union[dtype, array], /) -> finfo: + """ + Array API compatible wrapper for :py:func:`np.finfo `. + + See its docstring for more information. + """ + return np.finfo(type) + +def iinfo(type: Union[dtype, array], /) -> iinfo: + """ + Array API compatible wrapper for :py:func:`np.iinfo `. + + See its docstring for more information. + """ + return np.iinfo(type) + +def result_type(*arrays_and_dtypes: Sequence[Union[array, dtype]]) -> dtype: + """ + Array API compatible wrapper for :py:func:`np.result_type `. + + See its docstring for more information. + """ + return np.result_type(*arrays_and_dtypes) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 9de4261acf7..a117c337069 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -294,6 +294,14 @@ def log10(x: array, /) -> array: """ return np.log10(x) +def logaddexp(x1: array, x2: array) -> array: + """ + Array API compatible wrapper for :py:func:`np.logaddexp `. + + See its docstring for more information. + """ + return np.logaddexp(x1, x2) + def logical_and(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.logical_and `. diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 0a75d727e61..4dfc215a7bf 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -4,10 +4,10 @@ import numpy as np -def unique(x: array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False, sorted: bool = True) -> Union[array, Tuple[array, ...]]: +def unique(x: array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False) -> Union[array, Tuple[array, ...]]: """ Array API compatible wrapper for :py:func:`np.unique `. See its docstring for more information. """ - return np.unique._implementation(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse, sorted=sorted) + return np.unique._implementation(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse) diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index e8867a29b33..3800b71565b 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -6,7 +6,8 @@ valid for inputs that match the given type annotations. """ -__all__ = ['Literal', 'Optional', 'Tuple', 'Union', 'array', 'device', 'dtype'] +__all__ = ['Literal', 'Optional', 'Tuple', 'Union', 'array', 'device', + 'dtype', 'SupportsDLPack', 'SupportsBufferProtocol', 'PyCapsule'] from typing import Literal, Optional, Tuple, Union, TypeVar @@ -16,3 +17,6 @@ device = TypeVar('device') dtype = Literal[np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64, np.float32, np.float64] +SupportsDLPack = TypeVar('SupportsDLPack') +SupportsBufferProtocol = TypeVar('SupportsBufferProtocol') +PyCapsule = TypeVar('PyCapsule') From d9438ad1a8f4f44809fce6c19096436159f5fb03 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 18:03:29 -0700 Subject: [PATCH 022/151] Start implementing wrapper object for the array API So far, it just is a wrapper with all the methods defined in the spec, which all pass through. The next step is to make it so that the methods that behave differently actually work as the spec describes. We also still need to modify all the array_api functions to return this wrapper object instead of np.ndarray. --- numpy/_array_api/_array_object.py | 479 ++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 numpy/_array_api/_array_object.py diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py new file mode 100644 index 00000000000..5ce650ae915 --- /dev/null +++ b/numpy/_array_api/_array_object.py @@ -0,0 +1,479 @@ +""" +Wrapper class around the ndarray object for the array API standard. + +The array API standard defines some behaviors differently than ndarray, in +particular, type promotion rules are different (the standard has no +value-based casting). The standard also specifies a more limited subset of +array methods and functionalities than are implemented on ndarray. Since the +goal of the array_api namespace is to be a minimal implementation of the array +API standard, we need to define a separate wrapper class for the array_api +namespace. + +The standard compliant class is only a wrapper class. It is *not* a subclass +of ndarray. +""" + +from __future__ import annotations + +from enum import IntEnum +from ._types import Optional, PyCapsule, Tuple, Union, array + +class ndarray: + # Use a custom constructor instead of __init__, as manually initializing + # this class is not supported API. + @classmethod + def _new(cls, x, /): + """ + This is a private method for initializing the array API ndarray + object. + + Functions outside of the array_api submodule should not use this + method. Use one of the creation functions instead, such as + ``asarray``. + + """ + obj = super().__new__(cls) + obj._array = x + return obj + + # Prevent ndarray() from working + def __new__(cls, *args, **kwargs): + raise TypeError("The array_api ndarray object should not be instantiated directly. Use an array creation function, such as asarray(), instead.") + + def __abs__(x: array, /) -> array: + """ + Performs the operation __abs__. + """ + res = x._array.__abs__(x) + return x.__class__._new(res) + + def __add__(x1: array, x2: array, /) -> array: + """ + Performs the operation __add__. + """ + res = x1._array.__add__(x1, x2) + return x1.__class__._new(res) + + def __and__(x1: array, x2: array, /) -> array: + """ + Performs the operation __and__. + """ + res = x1._array.__and__(x1, x2) + return x1.__class__._new(res) + + def __bool__(x: array, /) -> bool: + """ + Performs the operation __bool__. + """ + res = x._array.__bool__(x) + return x.__class__._new(res) + + def __dlpack__(x: array, /, *, stream: Optional[int] = None) -> PyCapsule: + """ + Performs the operation __dlpack__. + """ + res = x._array.__dlpack__(x, stream=None) + return x.__class__._new(res) + + def __dlpack_device__(x: array, /) -> Tuple[IntEnum, int]: + """ + Performs the operation __dlpack_device__. + """ + res = x._array.__dlpack_device__(x) + return x.__class__._new(res) + + def __eq__(x1: array, x2: array, /) -> array: + """ + Performs the operation __eq__. + """ + res = x1._array.__eq__(x1, x2) + return x1.__class__._new(res) + + def __float__(x: array, /) -> float: + """ + Performs the operation __float__. + """ + res = x._array.__float__(x) + return x.__class__._new(res) + + def __floordiv__(x1: array, x2: array, /) -> array: + """ + Performs the operation __floordiv__. + """ + res = x1._array.__floordiv__(x1, x2) + return x1.__class__._new(res) + + def __ge__(x1: array, x2: array, /) -> array: + """ + Performs the operation __ge__. + """ + res = x1._array.__ge__(x1, x2) + return x1.__class__._new(res) + + def __getitem__(x: array, key: Union[int, slice, Tuple[Union[int, slice], ...], array], /) -> array: + """ + Performs the operation __getitem__. + """ + res = x._array.__getitem__(x, key) + return x.__class__._new(res) + + def __gt__(x1: array, x2: array, /) -> array: + """ + Performs the operation __gt__. + """ + res = x1._array.__gt__(x1, x2) + return x1.__class__._new(res) + + def __int__(x: array, /) -> int: + """ + Performs the in-place operation __int__. + """ + x._array.__int__(x) + + def __invert__(x: array, /) -> array: + """ + Performs the in-place operation __invert__. + """ + x._array.__invert__(x) + + def __le__(x1: array, x2: array, /) -> array: + """ + Performs the operation __le__. + """ + res = x1._array.__le__(x1, x2) + return x1.__class__._new(res) + + def __len__(x, /): + """ + Performs the operation __len__. + """ + res = x._array.__len__(x) + return x.__class__._new(res) + + def __lshift__(x1: array, x2: array, /) -> array: + """ + Performs the operation __lshift__. + """ + res = x1._array.__lshift__(x1, x2) + return x1.__class__._new(res) + + def __lt__(x1: array, x2: array, /) -> array: + """ + Performs the operation __lt__. + """ + res = x1._array.__lt__(x1, x2) + return x1.__class__._new(res) + + def __matmul__(x1: array, x2: array, /) -> array: + """ + Performs the operation __matmul__. + """ + res = x1._array.__matmul__(x1, x2) + return x1.__class__._new(res) + + def __mod__(x1: array, x2: array, /) -> array: + """ + Performs the operation __mod__. + """ + res = x1._array.__mod__(x1, x2) + return x1.__class__._new(res) + + def __mul__(x1: array, x2: array, /) -> array: + """ + Performs the operation __mul__. + """ + res = x1._array.__mul__(x1, x2) + return x1.__class__._new(res) + + def __ne__(x1: array, x2: array, /) -> array: + """ + Performs the operation __ne__. + """ + res = x1._array.__ne__(x1, x2) + return x1.__class__._new(res) + + def __neg__(x: array, /) -> array: + """ + Performs the operation __neg__. + """ + res = x._array.__neg__(x) + return x.__class__._new(res) + + def __or__(x1: array, x2: array, /) -> array: + """ + Performs the operation __or__. + """ + res = x1._array.__or__(x1, x2) + return x1.__class__._new(res) + + def __pos__(x: array, /) -> array: + """ + Performs the operation __pos__. + """ + res = x._array.__pos__(x) + return x.__class__._new(res) + + def __pow__(x1: array, x2: array, /) -> array: + """ + Performs the operation __pow__. + """ + res = x1._array.__pow__(x1, x2) + return x1.__class__._new(res) + + def __rshift__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rshift__. + """ + res = x1._array.__rshift__(x1, x2) + return x1.__class__._new(res) + + def __setitem__(x, key, value, /): + """ + Performs the operation __setitem__. + """ + res = x._array.__setitem__(x, key, value) + return x.__class__._new(res) + + def __sub__(x1: array, x2: array, /) -> array: + """ + Performs the operation __sub__. + """ + res = x1._array.__sub__(x1, x2) + return x1.__class__._new(res) + + def __truediv__(x1: array, x2: array, /) -> array: + """ + Performs the operation __truediv__. + """ + res = x1._array.__truediv__(x1, x2) + return x1.__class__._new(res) + + def __xor__(x1: array, x2: array, /) -> array: + """ + Performs the operation __xor__. + """ + res = x1._array.__xor__(x1, x2) + return x1.__class__._new(res) + + def __iadd__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __iadd__. + """ + x1._array.__iadd__(x1, x2) + + def __radd__(x1: array, x2: array, /) -> array: + """ + Performs the operation __radd__. + """ + res = x1._array.__radd__(x1, x2) + return x1.__class__._new(res) + + def __iand__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __iand__. + """ + x1._array.__iand__(x1, x2) + + def __rand__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rand__. + """ + res = x1._array.__rand__(x1, x2) + return x1.__class__._new(res) + + def __ifloordiv__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __ifloordiv__. + """ + x1._array.__ifloordiv__(x1, x2) + + def __rfloordiv__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rfloordiv__. + """ + res = x1._array.__rfloordiv__(x1, x2) + return x1.__class__._new(res) + + def __ilshift__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __ilshift__. + """ + x1._array.__ilshift__(x1, x2) + + def __rlshift__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rlshift__. + """ + res = x1._array.__rlshift__(x1, x2) + return x1.__class__._new(res) + + def __imatmul__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __imatmul__. + """ + x1._array.__imatmul__(x1, x2) + + def __rmatmul__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rmatmul__. + """ + res = x1._array.__rmatmul__(x1, x2) + return x1.__class__._new(res) + + def __imod__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __imod__. + """ + x1._array.__imod__(x1, x2) + + def __rmod__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rmod__. + """ + res = x1._array.__rmod__(x1, x2) + return x1.__class__._new(res) + + def __imul__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __imul__. + """ + x1._array.__imul__(x1, x2) + + def __rmul__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rmul__. + """ + res = x1._array.__rmul__(x1, x2) + return x1.__class__._new(res) + + def __ior__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __ior__. + """ + x1._array.__ior__(x1, x2) + + def __ror__(x1: array, x2: array, /) -> array: + """ + Performs the operation __ror__. + """ + res = x1._array.__ror__(x1, x2) + return x1.__class__._new(res) + + def __ipow__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __ipow__. + """ + x1._array.__ipow__(x1, x2) + + def __rpow__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rpow__. + """ + res = x1._array.__rpow__(x1, x2) + return x1.__class__._new(res) + + def __irshift__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __irshift__. + """ + x1._array.__irshift__(x1, x2) + + def __rrshift__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rrshift__. + """ + res = x1._array.__rrshift__(x1, x2) + return x1.__class__._new(res) + + def __isub__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __isub__. + """ + x1._array.__isub__(x1, x2) + + def __rsub__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rsub__. + """ + res = x1._array.__rsub__(x1, x2) + return x1.__class__._new(res) + + def __itruediv__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __itruediv__. + """ + x1._array.__itruediv__(x1, x2) + + def __rtruediv__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rtruediv__. + """ + res = x1._array.__rtruediv__(x1, x2) + return x1.__class__._new(res) + + def __ixor__(x1: array, x2: array, /) -> array: + """ + Performs the in-place operation __ixor__. + """ + x1._array.__ixor__(x1, x2) + + def __rxor__(x1: array, x2: array, /) -> array: + """ + Performs the operation __rxor__. + """ + res = x1._array.__rxor__(x1, x2) + return x1.__class__._new(res) + + @property + def dtype(self): + """ + Array API compatible wrapper for :py:meth:`np.ndaray.dtype `. + + See its docstring for more information. + """ + return self._array.dtype + + @property + def device(self): + """ + Array API compatible wrapper for :py:meth:`np.ndaray.device `. + + See its docstring for more information. + """ + return self._array.device + + @property + def ndim(self): + """ + Array API compatible wrapper for :py:meth:`np.ndaray.ndim `. + + See its docstring for more information. + """ + return self._array.ndim + + @property + def shape(self): + """ + Array API compatible wrapper for :py:meth:`np.ndaray.shape `. + + See its docstring for more information. + """ + return self._array.shape + + @property + def size(self): + """ + Array API compatible wrapper for :py:meth:`np.ndaray.size `. + + See its docstring for more information. + """ + return self._array.size + + @property + def T(self): + """ + Array API compatible wrapper for :py:meth:`np.ndaray.T `. + + See its docstring for more information. + """ + return self._array.T From e233a0ac8ac3f740de3537c2cc874446babb21ce Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 18:11:54 -0700 Subject: [PATCH 023/151] Add some missing names in the array_api namespace __all__ --- numpy/_array_api/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 6afddb26a5b..43b2d4ce302 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -84,17 +84,21 @@ __all__ += ['e', 'inf', 'nan', 'pi'] -from ._creation_functions import arange, empty, empty_like, eye, full, full_like, linspace, ones, ones_like, zeros, zeros_like +from ._creation_functions import asarray, arange, empty, empty_like, eye, from_dlpack, full, full_like, linspace, ones, ones_like, zeros, zeros_like -__all__ += ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] +__all__ += ['asarray', 'arange', 'empty', 'empty_like', 'eye', 'from_dlpack', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] + +from ._data_type_functions import finfo, iinfo, result_type + +__all__ += ['finfo', 'iinfo', 'result_type'] from ._dtypes import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool __all__ += ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] -from ._elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc +from ._elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logaddexp, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc -__all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] +__all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logaddexp', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] from ._linear_algebra_functions import cross, det, diagonal, inv, norm, outer, trace, transpose From c791956135e3a1e63ca1e51ec5a1a79e65015ce8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 18:14:49 -0700 Subject: [PATCH 024/151] Fix the copy keyword argument in the array_api namespace asarray() --- numpy/_array_api/_creation_functions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index d015734ff15..bd607234dfd 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -14,7 +14,10 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.asarray(obj, dtype=dtype, copy=copy) + if copy is not None: + # Note: copy is not yet implemented in np.asarray + raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") + return np.asarray(obj, dtype=dtype) def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ From 853a18de30219a0d25709caac0410de6dfeb4e95 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 18:21:35 -0700 Subject: [PATCH 025/151] Implement a simple passthrough __str__ and __repr__ on the array_api ndarray class These methods aren't required by the spec, but without them, the array object is harder to use interactively. --- numpy/_array_api/_array_object.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 5ce650ae915..09f5e5710f1 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -40,6 +40,23 @@ def _new(cls, x, /): def __new__(cls, *args, **kwargs): raise TypeError("The array_api ndarray object should not be instantiated directly. Use an array creation function, such as asarray(), instead.") + # These functions are not required by the spec, but are implemented for + # the sake of usability. + + def __str__(x: array, /) -> str: + """ + Performs the operation __str__. + """ + return x._array.__str__() + + def __repr__(x: array, /) -> str: + """ + Performs the operation __repr__. + """ + return x._array.__repr__() + + # Everything below this is required by the spec. + def __abs__(x: array, /) -> array: """ Performs the operation __abs__. From a42f71ac8ac559d0ef770bdfd2f62bd1be4848d5 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 18:54:02 -0700 Subject: [PATCH 026/151] Only allow supported dtypes in the array_api namespace asarray() --- numpy/_array_api/_creation_functions.py | 7 ++++++- numpy/_array_api/_dtypes.py | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index bd607234dfd..06d9e6dad8d 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -11,13 +11,18 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su See its docstring for more information. """ + from ._array_object import ndarray + from . import _dtypes if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") if copy is not None: # Note: copy is not yet implemented in np.asarray raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") - return np.asarray(obj, dtype=dtype) + res = np.asarray(obj, dtype=dtype) + if res.dtype not in _dtypes._all_dtypes: + raise TypeError(f"The array_api namespace does not support the dtype {res.dtype}") + return ndarray._new(res) def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index acf87fd82ff..f5e25355f2d 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -1,3 +1,6 @@ from .. import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64 # Note: This name is changed from .. import bool_ as bool + +_all_dtypes = [int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64, bool] From cd7092078b8f74ac5f873c9547676688ffd22276 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 23 Feb 2021 18:54:43 -0700 Subject: [PATCH 027/151] Support array_api.ndarray in array_api.asarray() --- numpy/_array_api/_creation_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 06d9e6dad8d..6d9a767b4db 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -19,6 +19,8 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su if copy is not None: # Note: copy is not yet implemented in np.asarray raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") + if isinstance(obj, ndarray): + return obj res = np.asarray(obj, dtype=dtype) if res.dtype not in _dtypes._all_dtypes: raise TypeError(f"The array_api namespace does not support the dtype {res.dtype}") From 3b9c910ab1687f5af6c2d20fd737704fe39706e2 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 17:35:53 -0700 Subject: [PATCH 028/151] Return ndarray in the array_api namespace elementwise functions --- numpy/_array_api/_elementwise_functions.py | 113 +++++++++++---------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index a117c337069..abb7ef4dd76 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import array +from ._array_object import ndarray import numpy as np @@ -10,7 +11,7 @@ def abs(x: array, /) -> array: See its docstring for more information. """ - return np.abs(x) + return ndarray._new(np.abs(x._array)) def acos(x: array, /) -> array: """ @@ -19,7 +20,7 @@ def acos(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arccos(x) + return ndarray._new(np.arccos(x._array)) def acosh(x: array, /) -> array: """ @@ -28,7 +29,7 @@ def acosh(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arccosh(x) + return ndarray._new(np.arccosh(x._array)) def add(x1: array, x2: array, /) -> array: """ @@ -36,7 +37,7 @@ def add(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.add(x1, x2) + return ndarray._new(np.add(x1._array, x2._array)) def asin(x: array, /) -> array: """ @@ -45,7 +46,7 @@ def asin(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arcsin(x) + return ndarray._new(np.arcsin(x._array)) def asinh(x: array, /) -> array: """ @@ -54,7 +55,7 @@ def asinh(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arcsinh(x) + return ndarray._new(np.arcsinh(x._array)) def atan(x: array, /) -> array: """ @@ -63,7 +64,7 @@ def atan(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arctan(x) + return ndarray._new(np.arctan(x._array)) def atan2(x1: array, x2: array, /) -> array: """ @@ -72,7 +73,7 @@ def atan2(x1: array, x2: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arctan2(x1, x2) + return ndarray._new(np.arctan2(x1._array, x2._array)) def atanh(x: array, /) -> array: """ @@ -81,7 +82,7 @@ def atanh(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.arctanh(x) + return ndarray._new(np.arctanh(x._array)) def bitwise_and(x1: array, x2: array, /) -> array: """ @@ -89,7 +90,7 @@ def bitwise_and(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.bitwise_and(x1, x2) + return ndarray._new(np.bitwise_and(x1._array, x2._array)) def bitwise_left_shift(x1: array, x2: array, /) -> array: """ @@ -98,7 +99,7 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.left_shift(x1, x2) + return ndarray._new(np.left_shift(x1._array, x2._array)) def bitwise_invert(x: array, /) -> array: """ @@ -107,7 +108,7 @@ def bitwise_invert(x: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.invert(x) + return ndarray._new(np.invert(x._array)) def bitwise_or(x1: array, x2: array, /) -> array: """ @@ -115,7 +116,7 @@ def bitwise_or(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.bitwise_or(x1, x2) + return ndarray._new(np.bitwise_or(x1._array, x2._array)) def bitwise_right_shift(x1: array, x2: array, /) -> array: """ @@ -124,7 +125,7 @@ def bitwise_right_shift(x1: array, x2: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.right_shift(x1, x2) + return ndarray._new(np.right_shift(x1._array, x2._array)) def bitwise_xor(x1: array, x2: array, /) -> array: """ @@ -132,7 +133,7 @@ def bitwise_xor(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.bitwise_xor(x1, x2) + return ndarray._new(np.bitwise_xor(x1._array, x2._array)) def ceil(x: array, /) -> array: """ @@ -140,7 +141,7 @@ def ceil(x: array, /) -> array: See its docstring for more information. """ - return np.ceil(x) + return ndarray._new(np.ceil(x._array)) def cos(x: array, /) -> array: """ @@ -148,7 +149,7 @@ def cos(x: array, /) -> array: See its docstring for more information. """ - return np.cos(x) + return ndarray._new(np.cos(x._array)) def cosh(x: array, /) -> array: """ @@ -156,7 +157,7 @@ def cosh(x: array, /) -> array: See its docstring for more information. """ - return np.cosh(x) + return ndarray._new(np.cosh(x._array)) def divide(x1: array, x2: array, /) -> array: """ @@ -164,7 +165,7 @@ def divide(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.divide(x1, x2) + return ndarray._new(np.divide(x1._array, x2._array)) def equal(x1: array, x2: array, /) -> array: """ @@ -172,7 +173,7 @@ def equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.equal(x1, x2) + return ndarray._new(np.equal(x1._array, x2._array)) def exp(x: array, /) -> array: """ @@ -180,7 +181,7 @@ def exp(x: array, /) -> array: See its docstring for more information. """ - return np.exp(x) + return ndarray._new(np.exp(x._array)) def expm1(x: array, /) -> array: """ @@ -188,7 +189,7 @@ def expm1(x: array, /) -> array: See its docstring for more information. """ - return np.expm1(x) + return ndarray._new(np.expm1(x._array)) def floor(x: array, /) -> array: """ @@ -196,7 +197,7 @@ def floor(x: array, /) -> array: See its docstring for more information. """ - return np.floor(x) + return ndarray._new(np.floor(x._array)) def floor_divide(x1: array, x2: array, /) -> array: """ @@ -204,7 +205,7 @@ def floor_divide(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.floor_divide(x1, x2) + return ndarray._new(np.floor_divide(x1._array, x2._array)) def greater(x1: array, x2: array, /) -> array: """ @@ -212,7 +213,7 @@ def greater(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.greater(x1, x2) + return ndarray._new(np.greater(x1._array, x2._array)) def greater_equal(x1: array, x2: array, /) -> array: """ @@ -220,7 +221,7 @@ def greater_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.greater_equal(x1, x2) + return ndarray._new(np.greater_equal(x1._array, x2._array)) def isfinite(x: array, /) -> array: """ @@ -228,7 +229,7 @@ def isfinite(x: array, /) -> array: See its docstring for more information. """ - return np.isfinite(x) + return ndarray._new(np.isfinite(x._array)) def isinf(x: array, /) -> array: """ @@ -236,7 +237,7 @@ def isinf(x: array, /) -> array: See its docstring for more information. """ - return np.isinf(x) + return ndarray._new(np.isinf(x._array)) def isnan(x: array, /) -> array: """ @@ -244,7 +245,7 @@ def isnan(x: array, /) -> array: See its docstring for more information. """ - return np.isnan(x) + return ndarray._new(np.isnan(x._array)) def less(x1: array, x2: array, /) -> array: """ @@ -252,7 +253,7 @@ def less(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.less(x1, x2) + return ndarray._new(np.less(x1._array, x2._array)) def less_equal(x1: array, x2: array, /) -> array: """ @@ -260,7 +261,7 @@ def less_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.less_equal(x1, x2) + return ndarray._new(np.less_equal(x1._array, x2._array)) def log(x: array, /) -> array: """ @@ -268,7 +269,7 @@ def log(x: array, /) -> array: See its docstring for more information. """ - return np.log(x) + return ndarray._new(np.log(x._array)) def log1p(x: array, /) -> array: """ @@ -276,7 +277,7 @@ def log1p(x: array, /) -> array: See its docstring for more information. """ - return np.log1p(x) + return ndarray._new(np.log1p(x._array)) def log2(x: array, /) -> array: """ @@ -284,7 +285,7 @@ def log2(x: array, /) -> array: See its docstring for more information. """ - return np.log2(x) + return ndarray._new(np.log2(x._array)) def log10(x: array, /) -> array: """ @@ -292,7 +293,7 @@ def log10(x: array, /) -> array: See its docstring for more information. """ - return np.log10(x) + return ndarray._new(np.log10(x._array)) def logaddexp(x1: array, x2: array) -> array: """ @@ -300,7 +301,7 @@ def logaddexp(x1: array, x2: array) -> array: See its docstring for more information. """ - return np.logaddexp(x1, x2) + return ndarray._new(np.logaddexp(x1._array, x2._array)) def logical_and(x1: array, x2: array, /) -> array: """ @@ -308,7 +309,7 @@ def logical_and(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.logical_and(x1, x2) + return ndarray._new(np.logical_and(x1._array, x2._array)) def logical_not(x: array, /) -> array: """ @@ -316,7 +317,7 @@ def logical_not(x: array, /) -> array: See its docstring for more information. """ - return np.logical_not(x) + return ndarray._new(np.logical_not(x._array)) def logical_or(x1: array, x2: array, /) -> array: """ @@ -324,7 +325,7 @@ def logical_or(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.logical_or(x1, x2) + return ndarray._new(np.logical_or(x1._array, x2._array)) def logical_xor(x1: array, x2: array, /) -> array: """ @@ -332,7 +333,7 @@ def logical_xor(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.logical_xor(x1, x2) + return ndarray._new(np.logical_xor(x1._array, x2._array)) def multiply(x1: array, x2: array, /) -> array: """ @@ -340,7 +341,7 @@ def multiply(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.multiply(x1, x2) + return ndarray._new(np.multiply(x1._array, x2._array)) def negative(x: array, /) -> array: """ @@ -348,7 +349,7 @@ def negative(x: array, /) -> array: See its docstring for more information. """ - return np.negative(x) + return ndarray._new(np.negative(x._array)) def not_equal(x1: array, x2: array, /) -> array: """ @@ -356,7 +357,7 @@ def not_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.not_equal(x1, x2) + return ndarray._new(np.not_equal(x1._array, x2._array)) def positive(x: array, /) -> array: """ @@ -364,7 +365,7 @@ def positive(x: array, /) -> array: See its docstring for more information. """ - return np.positive(x) + return ndarray._new(np.positive(x._array)) def pow(x1: array, x2: array, /) -> array: """ @@ -373,7 +374,7 @@ def pow(x1: array, x2: array, /) -> array: See its docstring for more information. """ # Note: the function name is different here - return np.power(x1, x2) + return ndarray._new(np.power(x1._array, x2._array)) def remainder(x1: array, x2: array, /) -> array: """ @@ -381,7 +382,7 @@ def remainder(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.remainder(x1, x2) + return ndarray._new(np.remainder(x1._array, x2._array)) def round(x: array, /) -> array: """ @@ -389,7 +390,7 @@ def round(x: array, /) -> array: See its docstring for more information. """ - return np.round._implementation(x) + return ndarray._new(np.round._implementation(x._array)) def sign(x: array, /) -> array: """ @@ -397,7 +398,7 @@ def sign(x: array, /) -> array: See its docstring for more information. """ - return np.sign(x) + return ndarray._new(np.sign(x._array)) def sin(x: array, /) -> array: """ @@ -405,7 +406,7 @@ def sin(x: array, /) -> array: See its docstring for more information. """ - return np.sin(x) + return ndarray._new(np.sin(x._array)) def sinh(x: array, /) -> array: """ @@ -413,7 +414,7 @@ def sinh(x: array, /) -> array: See its docstring for more information. """ - return np.sinh(x) + return ndarray._new(np.sinh(x._array)) def square(x: array, /) -> array: """ @@ -421,7 +422,7 @@ def square(x: array, /) -> array: See its docstring for more information. """ - return np.square(x) + return ndarray._new(np.square(x._array)) def sqrt(x: array, /) -> array: """ @@ -429,7 +430,7 @@ def sqrt(x: array, /) -> array: See its docstring for more information. """ - return np.sqrt(x) + return ndarray._new(np.sqrt(x._array)) def subtract(x1: array, x2: array, /) -> array: """ @@ -437,7 +438,7 @@ def subtract(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.subtract(x1, x2) + return ndarray._new(np.subtract(x1._array, x2._array)) def tan(x: array, /) -> array: """ @@ -445,7 +446,7 @@ def tan(x: array, /) -> array: See its docstring for more information. """ - return np.tan(x) + return ndarray._new(np.tan(x._array)) def tanh(x: array, /) -> array: """ @@ -453,7 +454,7 @@ def tanh(x: array, /) -> array: See its docstring for more information. """ - return np.tanh(x) + return ndarray._new(np.tanh(x._array)) def trunc(x: array, /) -> array: """ @@ -461,4 +462,4 @@ def trunc(x: array, /) -> array: See its docstring for more information. """ - return np.trunc(x) + return ndarray._new(np.trunc(x._array)) From 6e36bfce6fae5ce1a0aa2a71eee3d953366ba439 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 17:37:27 -0700 Subject: [PATCH 029/151] Use a different repr form for array_api.ndarray than array --- numpy/_array_api/_array_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 09f5e5710f1..99e6147f590 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -47,13 +47,13 @@ def __str__(x: array, /) -> str: """ Performs the operation __str__. """ - return x._array.__str__() + return x._array.__str__().replace('array', 'ndarray') def __repr__(x: array, /) -> str: """ Performs the operation __repr__. """ - return x._array.__repr__() + return x._array.__repr__().replace('array', 'ndarray') # Everything below this is required by the spec. From 061fecb0c68d35ab8761ad5f9f1b05f3c3bd293b Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 17:38:07 -0700 Subject: [PATCH 030/151] Fix the dunder methods on array_api.ndarray --- numpy/_array_api/_array_object.py | 160 ++++++++++++++++-------------- 1 file changed, 88 insertions(+), 72 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 99e6147f590..4c4abeb4acf 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -17,6 +17,7 @@ from enum import IntEnum from ._types import Optional, PyCapsule, Tuple, Union, array +from ._creation_functions import asarray class ndarray: # Use a custom constructor instead of __init__, as manually initializing @@ -61,384 +62,399 @@ def __abs__(x: array, /) -> array: """ Performs the operation __abs__. """ - res = x._array.__abs__(x) + res = x._array.__abs__() return x.__class__._new(res) def __add__(x1: array, x2: array, /) -> array: """ Performs the operation __add__. """ - res = x1._array.__add__(x1, x2) + res = x1._array.__add__(asarray(x2)._array) return x1.__class__._new(res) def __and__(x1: array, x2: array, /) -> array: """ Performs the operation __and__. """ - res = x1._array.__and__(x1, x2) + res = x1._array.__and__(asarray(x2)._array) return x1.__class__._new(res) def __bool__(x: array, /) -> bool: """ Performs the operation __bool__. """ - res = x._array.__bool__(x) + res = x._array.__bool__() return x.__class__._new(res) def __dlpack__(x: array, /, *, stream: Optional[int] = None) -> PyCapsule: """ Performs the operation __dlpack__. """ - res = x._array.__dlpack__(x, stream=None) + res = x._array.__dlpack__(stream=None) return x.__class__._new(res) def __dlpack_device__(x: array, /) -> Tuple[IntEnum, int]: """ Performs the operation __dlpack_device__. """ - res = x._array.__dlpack_device__(x) + res = x._array.__dlpack_device__() return x.__class__._new(res) def __eq__(x1: array, x2: array, /) -> array: """ Performs the operation __eq__. """ - res = x1._array.__eq__(x1, x2) + res = x1._array.__eq__(asarray(x2)._array) return x1.__class__._new(res) def __float__(x: array, /) -> float: """ Performs the operation __float__. """ - res = x._array.__float__(x) + res = x._array.__float__() return x.__class__._new(res) def __floordiv__(x1: array, x2: array, /) -> array: """ Performs the operation __floordiv__. """ - res = x1._array.__floordiv__(x1, x2) + res = x1._array.__floordiv__(asarray(x2)._array) return x1.__class__._new(res) def __ge__(x1: array, x2: array, /) -> array: """ Performs the operation __ge__. """ - res = x1._array.__ge__(x1, x2) + res = x1._array.__ge__(asarray(x2)._array) return x1.__class__._new(res) def __getitem__(x: array, key: Union[int, slice, Tuple[Union[int, slice], ...], array], /) -> array: """ Performs the operation __getitem__. """ - res = x._array.__getitem__(x, key) + res = x._array.__getitem__(asarray(key)._array) return x.__class__._new(res) def __gt__(x1: array, x2: array, /) -> array: """ Performs the operation __gt__. """ - res = x1._array.__gt__(x1, x2) + res = x1._array.__gt__(asarray(x2)._array) return x1.__class__._new(res) def __int__(x: array, /) -> int: """ - Performs the in-place operation __int__. + Performs the operation __int__. """ - x._array.__int__(x) + res = x._array.__int__() + return x.__class__._new(res) def __invert__(x: array, /) -> array: """ - Performs the in-place operation __invert__. + Performs the operation __invert__. """ - x._array.__invert__(x) + res = x._array.__invert__() + return x.__class__._new(res) def __le__(x1: array, x2: array, /) -> array: """ Performs the operation __le__. """ - res = x1._array.__le__(x1, x2) + res = x1._array.__le__(asarray(x2)._array) return x1.__class__._new(res) def __len__(x, /): """ Performs the operation __len__. """ - res = x._array.__len__(x) + res = x._array.__len__() return x.__class__._new(res) def __lshift__(x1: array, x2: array, /) -> array: """ Performs the operation __lshift__. """ - res = x1._array.__lshift__(x1, x2) + res = x1._array.__lshift__(asarray(x2)._array) return x1.__class__._new(res) def __lt__(x1: array, x2: array, /) -> array: """ Performs the operation __lt__. """ - res = x1._array.__lt__(x1, x2) + res = x1._array.__lt__(asarray(x2)._array) return x1.__class__._new(res) def __matmul__(x1: array, x2: array, /) -> array: """ Performs the operation __matmul__. """ - res = x1._array.__matmul__(x1, x2) + res = x1._array.__matmul__(asarray(x2)._array) return x1.__class__._new(res) def __mod__(x1: array, x2: array, /) -> array: """ Performs the operation __mod__. """ - res = x1._array.__mod__(x1, x2) + res = x1._array.__mod__(asarray(x2)._array) return x1.__class__._new(res) def __mul__(x1: array, x2: array, /) -> array: """ Performs the operation __mul__. """ - res = x1._array.__mul__(x1, x2) + res = x1._array.__mul__(asarray(x2)._array) return x1.__class__._new(res) def __ne__(x1: array, x2: array, /) -> array: """ Performs the operation __ne__. """ - res = x1._array.__ne__(x1, x2) + res = x1._array.__ne__(asarray(x2)._array) return x1.__class__._new(res) def __neg__(x: array, /) -> array: """ Performs the operation __neg__. """ - res = x._array.__neg__(x) + res = x._array.__neg__() return x.__class__._new(res) def __or__(x1: array, x2: array, /) -> array: """ Performs the operation __or__. """ - res = x1._array.__or__(x1, x2) + res = x1._array.__or__(asarray(x2)._array) return x1.__class__._new(res) def __pos__(x: array, /) -> array: """ Performs the operation __pos__. """ - res = x._array.__pos__(x) + res = x._array.__pos__() return x.__class__._new(res) def __pow__(x1: array, x2: array, /) -> array: """ Performs the operation __pow__. """ - res = x1._array.__pow__(x1, x2) + res = x1._array.__pow__(asarray(x2)._array) return x1.__class__._new(res) def __rshift__(x1: array, x2: array, /) -> array: """ Performs the operation __rshift__. """ - res = x1._array.__rshift__(x1, x2) + res = x1._array.__rshift__(asarray(x2)._array) return x1.__class__._new(res) def __setitem__(x, key, value, /): """ Performs the operation __setitem__. """ - res = x._array.__setitem__(x, key, value) + res = x._array.__setitem__(asarray(key)._array, asarray(value)._array) return x.__class__._new(res) def __sub__(x1: array, x2: array, /) -> array: """ Performs the operation __sub__. """ - res = x1._array.__sub__(x1, x2) + res = x1._array.__sub__(asarray(x2)._array) return x1.__class__._new(res) def __truediv__(x1: array, x2: array, /) -> array: """ Performs the operation __truediv__. """ - res = x1._array.__truediv__(x1, x2) + res = x1._array.__truediv__(asarray(x2)._array) return x1.__class__._new(res) def __xor__(x1: array, x2: array, /) -> array: """ Performs the operation __xor__. """ - res = x1._array.__xor__(x1, x2) + res = x1._array.__xor__(asarray(x2)._array) return x1.__class__._new(res) def __iadd__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __iadd__. + Performs the operation __iadd__. """ - x1._array.__iadd__(x1, x2) + res = x1._array.__iadd__(asarray(x2)._array) + return x1.__class__._new(res) def __radd__(x1: array, x2: array, /) -> array: """ Performs the operation __radd__. """ - res = x1._array.__radd__(x1, x2) + res = x1._array.__radd__(asarray(x2)._array) return x1.__class__._new(res) def __iand__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __iand__. + Performs the operation __iand__. """ - x1._array.__iand__(x1, x2) + res = x1._array.__iand__(asarray(x2)._array) + return x1.__class__._new(res) def __rand__(x1: array, x2: array, /) -> array: """ Performs the operation __rand__. """ - res = x1._array.__rand__(x1, x2) + res = x1._array.__rand__(asarray(x2)._array) return x1.__class__._new(res) def __ifloordiv__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __ifloordiv__. + Performs the operation __ifloordiv__. """ - x1._array.__ifloordiv__(x1, x2) + res = x1._array.__ifloordiv__(asarray(x2)._array) + return x1.__class__._new(res) def __rfloordiv__(x1: array, x2: array, /) -> array: """ Performs the operation __rfloordiv__. """ - res = x1._array.__rfloordiv__(x1, x2) + res = x1._array.__rfloordiv__(asarray(x2)._array) return x1.__class__._new(res) def __ilshift__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __ilshift__. + Performs the operation __ilshift__. """ - x1._array.__ilshift__(x1, x2) + res = x1._array.__ilshift__(asarray(x2)._array) + return x1.__class__._new(res) def __rlshift__(x1: array, x2: array, /) -> array: """ Performs the operation __rlshift__. """ - res = x1._array.__rlshift__(x1, x2) + res = x1._array.__rlshift__(asarray(x2)._array) return x1.__class__._new(res) def __imatmul__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __imatmul__. + Performs the operation __imatmul__. """ - x1._array.__imatmul__(x1, x2) + res = x1._array.__imatmul__(asarray(x2)._array) + return x1.__class__._new(res) def __rmatmul__(x1: array, x2: array, /) -> array: """ Performs the operation __rmatmul__. """ - res = x1._array.__rmatmul__(x1, x2) + res = x1._array.__rmatmul__(asarray(x2)._array) return x1.__class__._new(res) def __imod__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __imod__. + Performs the operation __imod__. """ - x1._array.__imod__(x1, x2) + res = x1._array.__imod__(asarray(x2)._array) + return x1.__class__._new(res) def __rmod__(x1: array, x2: array, /) -> array: """ Performs the operation __rmod__. """ - res = x1._array.__rmod__(x1, x2) + res = x1._array.__rmod__(asarray(x2)._array) return x1.__class__._new(res) def __imul__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __imul__. + Performs the operation __imul__. """ - x1._array.__imul__(x1, x2) + res = x1._array.__imul__(asarray(x2)._array) + return x1.__class__._new(res) def __rmul__(x1: array, x2: array, /) -> array: """ Performs the operation __rmul__. """ - res = x1._array.__rmul__(x1, x2) + res = x1._array.__rmul__(asarray(x2)._array) return x1.__class__._new(res) def __ior__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __ior__. + Performs the operation __ior__. """ - x1._array.__ior__(x1, x2) + res = x1._array.__ior__(asarray(x2)._array) + return x1.__class__._new(res) def __ror__(x1: array, x2: array, /) -> array: """ Performs the operation __ror__. """ - res = x1._array.__ror__(x1, x2) + res = x1._array.__ror__(asarray(x2)._array) return x1.__class__._new(res) def __ipow__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __ipow__. + Performs the operation __ipow__. """ - x1._array.__ipow__(x1, x2) + res = x1._array.__ipow__(asarray(x2)._array) + return x1.__class__._new(res) def __rpow__(x1: array, x2: array, /) -> array: """ Performs the operation __rpow__. """ - res = x1._array.__rpow__(x1, x2) + res = x1._array.__rpow__(asarray(x2)._array) return x1.__class__._new(res) def __irshift__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __irshift__. + Performs the operation __irshift__. """ - x1._array.__irshift__(x1, x2) + res = x1._array.__irshift__(asarray(x2)._array) + return x1.__class__._new(res) def __rrshift__(x1: array, x2: array, /) -> array: """ Performs the operation __rrshift__. """ - res = x1._array.__rrshift__(x1, x2) + res = x1._array.__rrshift__(asarray(x2)._array) return x1.__class__._new(res) def __isub__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __isub__. + Performs the operation __isub__. """ - x1._array.__isub__(x1, x2) + res = x1._array.__isub__(asarray(x2)._array) + return x1.__class__._new(res) def __rsub__(x1: array, x2: array, /) -> array: """ Performs the operation __rsub__. """ - res = x1._array.__rsub__(x1, x2) + res = x1._array.__rsub__(asarray(x2)._array) return x1.__class__._new(res) def __itruediv__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __itruediv__. + Performs the operation __itruediv__. """ - x1._array.__itruediv__(x1, x2) + res = x1._array.__itruediv__(asarray(x2)._array) + return x1.__class__._new(res) def __rtruediv__(x1: array, x2: array, /) -> array: """ Performs the operation __rtruediv__. """ - res = x1._array.__rtruediv__(x1, x2) + res = x1._array.__rtruediv__(asarray(x2)._array) return x1.__class__._new(res) def __ixor__(x1: array, x2: array, /) -> array: """ - Performs the in-place operation __ixor__. + Performs the operation __ixor__. """ - x1._array.__ixor__(x1, x2) + res = x1._array.__ixor__(asarray(x2)._array) + return x1.__class__._new(res) def __rxor__(x1: array, x2: array, /) -> array: """ Performs the operation __rxor__. """ - res = x1._array.__rxor__(x1, x2) + res = x1._array.__rxor__(asarray(x2)._array) return x1.__class__._new(res) @property From 587613f056299766be2da00a64b5fa0ac31c84aa Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 17:38:41 -0700 Subject: [PATCH 031/151] Use ndarray in the array API creation functions --- numpy/_array_api/_creation_functions.py | 33 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 6d9a767b4db..ba5b4c87a79 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -32,10 +32,11 @@ def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = N See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.arange(start, stop=stop, step=step, dtype=dtype) + return ndarray._new(np.arange(start, stop=stop, step=step, dtype=dtype)) def empty(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -43,10 +44,11 @@ def empty(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = Non See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.empty(shape, dtype=dtype) + return ndarray._new(np.empty(shape, dtype=dtype)) def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -54,10 +56,11 @@ def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.empty_like._implementation(x, dtype=dtype) + return ndarray._new(np.empty_like._implementation(x._array, dtype=dtype)) def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -65,10 +68,11 @@ def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Opti See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.eye(N, M=M, k=k, dtype=dtype) + return ndarray._new(np.eye(N, M=M, k=k, dtype=dtype)) def from_dlpack(x: object, /) -> array: # Note: dlpack support is not yet implemented on ndarray @@ -80,10 +84,11 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], /, * See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.full(shape, fill_value, dtype=dtype) + return ndarray._new(np.full(shape, fill_value, dtype=dtype)) def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -91,10 +96,11 @@ def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dty See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.full_like._implementation(x, fill_value, dtype=dtype) + return ndarray._new(np.full_like._implementation(x._array, fill_value, dtype=dtype)) def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: """ @@ -102,10 +108,11 @@ def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint) + return ndarray._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) def ones(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -113,10 +120,11 @@ def ones(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.ones(shape, dtype=dtype) + return ndarray._new(np.ones(shape, dtype=dtype)) def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -124,10 +132,11 @@ def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[de See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.ones_like._implementation(x, dtype=dtype) + return ndarray._new(np.ones_like._implementation(x._array, dtype=dtype)) def zeros(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -135,10 +144,11 @@ def zeros(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = Non See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.zeros(shape, dtype=dtype) + return ndarray._new(np.zeros(shape, dtype=dtype)) def zeros_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -146,7 +156,8 @@ def zeros_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d See its docstring for more information. """ + from ._array_object import ndarray if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return np.zeros_like._implementation(x, dtype=dtype) + return ndarray._new(np.zeros_like._implementation(x._array, dtype=dtype)) From 892b536a36b89f362a845fd50959d6474ec2c5f4 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 17:41:18 -0700 Subject: [PATCH 032/151] Only allow the spec guaranteed dtypes in the array API elementwise functions The array API namespace is designed to be only those parts of specification that are required. So many things that work in NumPy but are not required by the array API specification will not work in the array_api namespace functions. For example, transcendental functions will only work with floating-point dtypes, because those are the only dtypes required to work by the array API specification. --- numpy/_array_api/_dtypes.py | 5 + numpy/_array_api/_elementwise_functions.py | 114 +++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index f5e25355f2d..d33ae1fce87 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -4,3 +4,8 @@ _all_dtypes = [int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool] +_boolean_dtypes = [bool] +_floating_dtypes = [float32, float64] +_integer_dtypes = [int8, int16, int32, int64, uint8, uint16, uint32, uint64] +_integer_or_boolean_dtypes = [bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64] +_numeric_dtypes = [float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64] diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index abb7ef4dd76..2357b337ca3 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, + _integer_dtypes, _integer_or_boolean_dtypes, _numeric_dtypes) from ._types import array from ._array_object import ndarray @@ -11,6 +13,8 @@ def abs(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in abs') return ndarray._new(np.abs(x._array)) def acos(x: array, /) -> array: @@ -19,6 +23,8 @@ def acos(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in acos') # Note: the function name is different here return ndarray._new(np.arccos(x._array)) @@ -28,6 +34,8 @@ def acosh(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in acosh') # Note: the function name is different here return ndarray._new(np.arccosh(x._array)) @@ -37,6 +45,8 @@ def add(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in add') return ndarray._new(np.add(x1._array, x2._array)) def asin(x: array, /) -> array: @@ -45,6 +55,8 @@ def asin(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in asin') # Note: the function name is different here return ndarray._new(np.arcsin(x._array)) @@ -54,6 +66,8 @@ def asinh(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in asinh') # Note: the function name is different here return ndarray._new(np.arcsinh(x._array)) @@ -63,6 +77,8 @@ def atan(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in atan') # Note: the function name is different here return ndarray._new(np.arctan(x._array)) @@ -72,6 +88,8 @@ def atan2(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in atan2') # Note: the function name is different here return ndarray._new(np.arctan2(x1._array, x2._array)) @@ -81,6 +99,8 @@ def atanh(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in atanh') # Note: the function name is different here return ndarray._new(np.arctanh(x._array)) @@ -90,6 +110,8 @@ def bitwise_and(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: + raise TypeError('Only integer_or_boolean dtypes are allowed in bitwise_and') return ndarray._new(np.bitwise_and(x1._array, x2._array)) def bitwise_left_shift(x1: array, x2: array, /) -> array: @@ -98,6 +120,8 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: + raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') # Note: the function name is different here return ndarray._new(np.left_shift(x1._array, x2._array)) @@ -107,6 +131,8 @@ def bitwise_invert(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _integer_or_boolean_dtypes: + raise TypeError('Only integer or boolean dtypes are allowed in bitwise_invert') # Note: the function name is different here return ndarray._new(np.invert(x._array)) @@ -116,6 +142,8 @@ def bitwise_or(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: + raise TypeError('Only integer or boolean dtypes are allowed in bitwise_or') return ndarray._new(np.bitwise_or(x1._array, x2._array)) def bitwise_right_shift(x1: array, x2: array, /) -> array: @@ -124,6 +152,8 @@ def bitwise_right_shift(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: + raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') # Note: the function name is different here return ndarray._new(np.right_shift(x1._array, x2._array)) @@ -133,6 +163,8 @@ def bitwise_xor(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: + raise TypeError('Only integer or boolean dtypes are allowed in bitwise_xor') return ndarray._new(np.bitwise_xor(x1._array, x2._array)) def ceil(x: array, /) -> array: @@ -141,6 +173,8 @@ def ceil(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in ceil') return ndarray._new(np.ceil(x._array)) def cos(x: array, /) -> array: @@ -149,6 +183,8 @@ def cos(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in cos') return ndarray._new(np.cos(x._array)) def cosh(x: array, /) -> array: @@ -157,6 +193,8 @@ def cosh(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in cosh') return ndarray._new(np.cosh(x._array)) def divide(x1: array, x2: array, /) -> array: @@ -165,6 +203,8 @@ def divide(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in divide') return ndarray._new(np.divide(x1._array, x2._array)) def equal(x1: array, x2: array, /) -> array: @@ -173,6 +213,8 @@ def equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _all_dtypes or x2.dtype not in _all_dtypes: + raise TypeError('Only array API spec dtypes are allowed in equal') return ndarray._new(np.equal(x1._array, x2._array)) def exp(x: array, /) -> array: @@ -181,6 +223,8 @@ def exp(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in exp') return ndarray._new(np.exp(x._array)) def expm1(x: array, /) -> array: @@ -189,6 +233,8 @@ def expm1(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in expm1') return ndarray._new(np.expm1(x._array)) def floor(x: array, /) -> array: @@ -197,6 +243,8 @@ def floor(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in floor') return ndarray._new(np.floor(x._array)) def floor_divide(x1: array, x2: array, /) -> array: @@ -205,6 +253,8 @@ def floor_divide(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in floor_divide') return ndarray._new(np.floor_divide(x1._array, x2._array)) def greater(x1: array, x2: array, /) -> array: @@ -213,6 +263,8 @@ def greater(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in greater') return ndarray._new(np.greater(x1._array, x2._array)) def greater_equal(x1: array, x2: array, /) -> array: @@ -221,6 +273,8 @@ def greater_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in greater_equal') return ndarray._new(np.greater_equal(x1._array, x2._array)) def isfinite(x: array, /) -> array: @@ -229,6 +283,8 @@ def isfinite(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in isfinite') return ndarray._new(np.isfinite(x._array)) def isinf(x: array, /) -> array: @@ -237,6 +293,8 @@ def isinf(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in isinf') return ndarray._new(np.isinf(x._array)) def isnan(x: array, /) -> array: @@ -245,6 +303,8 @@ def isnan(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in isnan') return ndarray._new(np.isnan(x._array)) def less(x1: array, x2: array, /) -> array: @@ -253,6 +313,8 @@ def less(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in less') return ndarray._new(np.less(x1._array, x2._array)) def less_equal(x1: array, x2: array, /) -> array: @@ -261,6 +323,8 @@ def less_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in less_equal') return ndarray._new(np.less_equal(x1._array, x2._array)) def log(x: array, /) -> array: @@ -269,6 +333,8 @@ def log(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in log') return ndarray._new(np.log(x._array)) def log1p(x: array, /) -> array: @@ -277,6 +343,8 @@ def log1p(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in log1p') return ndarray._new(np.log1p(x._array)) def log2(x: array, /) -> array: @@ -285,6 +353,8 @@ def log2(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in log2') return ndarray._new(np.log2(x._array)) def log10(x: array, /) -> array: @@ -293,6 +363,8 @@ def log10(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in log10') return ndarray._new(np.log10(x._array)) def logaddexp(x1: array, x2: array) -> array: @@ -301,6 +373,8 @@ def logaddexp(x1: array, x2: array) -> array: See its docstring for more information. """ + if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in logaddexp') return ndarray._new(np.logaddexp(x1._array, x2._array)) def logical_and(x1: array, x2: array, /) -> array: @@ -309,6 +383,8 @@ def logical_and(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: + raise TypeError('Only boolean dtypes are allowed in logical_and') return ndarray._new(np.logical_and(x1._array, x2._array)) def logical_not(x: array, /) -> array: @@ -317,6 +393,8 @@ def logical_not(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _boolean_dtypes: + raise TypeError('Only boolean dtypes are allowed in logical_not') return ndarray._new(np.logical_not(x._array)) def logical_or(x1: array, x2: array, /) -> array: @@ -325,6 +403,8 @@ def logical_or(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: + raise TypeError('Only boolean dtypes are allowed in logical_or') return ndarray._new(np.logical_or(x1._array, x2._array)) def logical_xor(x1: array, x2: array, /) -> array: @@ -333,6 +413,8 @@ def logical_xor(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: + raise TypeError('Only boolean dtypes are allowed in logical_xor') return ndarray._new(np.logical_xor(x1._array, x2._array)) def multiply(x1: array, x2: array, /) -> array: @@ -341,6 +423,8 @@ def multiply(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in multiply') return ndarray._new(np.multiply(x1._array, x2._array)) def negative(x: array, /) -> array: @@ -349,6 +433,8 @@ def negative(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in negative') return ndarray._new(np.negative(x._array)) def not_equal(x1: array, x2: array, /) -> array: @@ -357,6 +443,8 @@ def not_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _all_dtypes or x2.dtype not in _all_dtypes: + raise TypeError('Only array API spec dtypes are allowed in not_equal') return ndarray._new(np.not_equal(x1._array, x2._array)) def positive(x: array, /) -> array: @@ -365,6 +453,8 @@ def positive(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in positive') return ndarray._new(np.positive(x._array)) def pow(x1: array, x2: array, /) -> array: @@ -373,6 +463,8 @@ def pow(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in pow') # Note: the function name is different here return ndarray._new(np.power(x1._array, x2._array)) @@ -382,6 +474,8 @@ def remainder(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in remainder') return ndarray._new(np.remainder(x1._array, x2._array)) def round(x: array, /) -> array: @@ -390,6 +484,8 @@ def round(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in round') return ndarray._new(np.round._implementation(x._array)) def sign(x: array, /) -> array: @@ -398,6 +494,8 @@ def sign(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in sign') return ndarray._new(np.sign(x._array)) def sin(x: array, /) -> array: @@ -406,6 +504,8 @@ def sin(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in sin') return ndarray._new(np.sin(x._array)) def sinh(x: array, /) -> array: @@ -414,6 +514,8 @@ def sinh(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in sinh') return ndarray._new(np.sinh(x._array)) def square(x: array, /) -> array: @@ -422,6 +524,8 @@ def square(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in square') return ndarray._new(np.square(x._array)) def sqrt(x: array, /) -> array: @@ -430,6 +534,8 @@ def sqrt(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in sqrt') return ndarray._new(np.sqrt(x._array)) def subtract(x1: array, x2: array, /) -> array: @@ -438,6 +544,8 @@ def subtract(x1: array, x2: array, /) -> array: See its docstring for more information. """ + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in subtract') return ndarray._new(np.subtract(x1._array, x2._array)) def tan(x: array, /) -> array: @@ -446,6 +554,8 @@ def tan(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in tan') return ndarray._new(np.tan(x._array)) def tanh(x: array, /) -> array: @@ -454,6 +564,8 @@ def tanh(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in tanh') return ndarray._new(np.tanh(x._array)) def trunc(x: array, /) -> array: @@ -462,4 +574,6 @@ def trunc(x: array, /) -> array: See its docstring for more information. """ + if x.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in trunc') return ndarray._new(np.trunc(x._array)) From b7856e348d731551405bdf0dd41ff1b0416da129 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 17:46:25 -0700 Subject: [PATCH 033/151] Make an error message easier to read --- numpy/_array_api/_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index ba5b4c87a79..4be482199b2 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -23,7 +23,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su return obj res = np.asarray(obj, dtype=dtype) if res.dtype not in _dtypes._all_dtypes: - raise TypeError(f"The array_api namespace does not support the dtype {res.dtype}") + raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") return ndarray._new(res) def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: From 2ff635c7cbc8804a3956ddbf8165f536dffc2df5 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 18:22:06 -0700 Subject: [PATCH 034/151] Don't check if a dtype is in all_dtypes The array API namespace is not going to do type checking against arbitrary objects. An object that takes an array as input should assume that it will get an array API namespace array object. Passing a NumPy array or other type of object to any of the functions is undefined behavior, unless the type signature allows for it. --- numpy/_array_api/_elementwise_functions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 2357b337ca3..b48a38c3dd5 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, +from ._dtypes import (_boolean_dtypes, _floating_dtypes, _integer_dtypes, _integer_or_boolean_dtypes, _numeric_dtypes) from ._types import array from ._array_object import ndarray @@ -213,8 +213,6 @@ def equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ - if x1.dtype not in _all_dtypes or x2.dtype not in _all_dtypes: - raise TypeError('Only array API spec dtypes are allowed in equal') return ndarray._new(np.equal(x1._array, x2._array)) def exp(x: array, /) -> array: @@ -443,8 +441,6 @@ def not_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ - if x1.dtype not in _all_dtypes or x2.dtype not in _all_dtypes: - raise TypeError('Only array API spec dtypes are allowed in not_equal') return ndarray._new(np.not_equal(x1._array, x2._array)) def positive(x: array, /) -> array: From b933ebbe1aee58af38f05a341dc3952fc761d777 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 18:24:36 -0700 Subject: [PATCH 035/151] Allow dimension 0 arrays in the array API namespace full() and full_like() --- numpy/_array_api/_creation_functions.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 4be482199b2..197960211d6 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -2,6 +2,7 @@ from ._types import (Optional, SupportsDLPack, SupportsBufferProtocol, Tuple, Union, array, device, dtype) +from ._dtypes import _all_dtypes import numpy as np @@ -88,7 +89,14 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], /, * if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.full(shape, fill_value, dtype=dtype)) + if isinstance(fill_value, ndarray) and fill_value.ndim == 0: + fill_value = fill_value._array[...] + res = np.full(shape, fill_value, dtype=dtype) + if res.dtype not in _all_dtypes: + # This will happen if the fill value is not something that NumPy + # coerces to one of the acceptable dtypes. + raise TypeError("Invalid input to full") + return ndarray._new(res) def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -100,7 +108,12 @@ def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dty if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.full_like._implementation(x._array, fill_value, dtype=dtype)) + res = np.full_like._implementation(x._array, fill_value, dtype=dtype) + if res.dtype not in _all_dtypes: + # This will happen if the fill value is not something that NumPy + # coerces to one of the acceptable dtypes. + raise TypeError("Invalid input to full_like") + return ndarray._new(res) def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: """ From 73d2c1e1675ed9e7fe2bc389ec0079c5c6ce73ee Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 18:24:55 -0700 Subject: [PATCH 036/151] Make the array API constants into dimension 0 arrays The spec does not actually specify whether these should be dimension 0 arrays or Python floats (which they are in NumPy). However, making them dimension 0 arrays is cleaner, and ensures they also have all the methods and attributes that are implemented on the ndarray object. --- numpy/_array_api/_constants.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_constants.py b/numpy/_array_api/_constants.py index 075b8c3b964..5fde3462586 100644 --- a/numpy/_array_api/_constants.py +++ b/numpy/_array_api/_constants.py @@ -1 +1,9 @@ -from .. import e, inf, nan, pi +from ._array_object import ndarray +from ._dtypes import float64 + +import numpy as np + +e = ndarray._new(np.array(np.e, dtype=float64)) +inf = ndarray._new(np.array(np.inf, dtype=float64)) +nan = ndarray._new(np.array(np.nan, dtype=float64)) +pi = ndarray._new(np.array(np.pi, dtype=float64)) From 16030e4e6931b81997b6c2d8d0ef4f15e39b6057 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 26 Feb 2021 18:28:48 -0700 Subject: [PATCH 037/151] Clean up some imports --- numpy/_array_api/_creation_functions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 197960211d6..888f245582e 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -12,8 +12,9 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su See its docstring for more information. """ + # _array_object imports in this file are inside the functions to avoid + # circular imports from ._array_object import ndarray - from . import _dtypes if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") @@ -23,7 +24,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su if isinstance(obj, ndarray): return obj res = np.asarray(obj, dtype=dtype) - if res.dtype not in _dtypes._all_dtypes: + if res.dtype not in _all_dtypes: raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") return ndarray._new(res) From d40985cbb50aeadf276a1a9332e455ddc096e113 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 2 Mar 2021 16:03:56 -0700 Subject: [PATCH 038/151] Fix some dunder methods on that should not be converting things to arrays --- numpy/_array_api/_array_object.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 4c4abeb4acf..23f8ab33313 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -84,7 +84,7 @@ def __bool__(x: array, /) -> bool: Performs the operation __bool__. """ res = x._array.__bool__() - return x.__class__._new(res) + return res def __dlpack__(x: array, /, *, stream: Optional[int] = None) -> PyCapsule: """ @@ -112,7 +112,7 @@ def __float__(x: array, /) -> float: Performs the operation __float__. """ res = x._array.__float__() - return x.__class__._new(res) + return res def __floordiv__(x1: array, x2: array, /) -> array: """ @@ -132,7 +132,7 @@ def __getitem__(x: array, key: Union[int, slice, Tuple[Union[int, slice], ...], """ Performs the operation __getitem__. """ - res = x._array.__getitem__(asarray(key)._array) + res = x._array.__getitem__(key) return x.__class__._new(res) def __gt__(x1: array, x2: array, /) -> array: @@ -147,7 +147,7 @@ def __int__(x: array, /) -> int: Performs the operation __int__. """ res = x._array.__int__() - return x.__class__._new(res) + return res def __invert__(x: array, /) -> array: """ @@ -251,7 +251,7 @@ def __setitem__(x, key, value, /): """ Performs the operation __setitem__. """ - res = x._array.__setitem__(asarray(key)._array, asarray(value)._array) + res = x._array.__setitem__(key, asarray(value)._array) return x.__class__._new(res) def __sub__(x1: array, x2: array, /) -> array: From f1f9dca83213aa4b0e6495900482f1a180a014ae Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 2 Mar 2021 16:08:41 -0700 Subject: [PATCH 039/151] Make the array API manipulation functions use the array API ndarray object --- numpy/_array_api/_manipulation_functions.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index e312b18c51d..413dbb1b1ba 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import Optional, Tuple, Union, array +from ._array_object import ndarray import numpy as np @@ -10,8 +11,9 @@ def concat(arrays: Tuple[array], /, *, axis: Optional[int] = 0) -> array: See its docstring for more information. """ + arrays = tuple(a._array for a in arrays) # Note: the function name is different here - return np.concatenate(arrays, axis=axis) + return ndarray._new(np.concatenate(arrays, axis=axis)) def expand_dims(x: array, axis: int, /) -> array: """ @@ -19,7 +21,7 @@ def expand_dims(x: array, axis: int, /) -> array: See its docstring for more information. """ - return np.expand_dims._implementation(x, axis) + return ndarray._new(np.expand_dims._implementation(x._array, axis)) def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -27,7 +29,7 @@ def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> See its docstring for more information. """ - return np.flip._implementation(x, axis=axis) + return ndarray._new(np.flip._implementation(x._array, axis=axis)) def reshape(x: array, shape: Tuple[int, ...], /) -> array: """ @@ -35,7 +37,7 @@ def reshape(x: array, shape: Tuple[int, ...], /) -> array: See its docstring for more information. """ - return np.reshape._implementation(x, shape) + return ndarray._new(np.reshape._implementation(x._array, shape)) def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -43,7 +45,7 @@ def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Unio See its docstring for more information. """ - return np.roll._implementation(x, shift, axis=axis) + return ndarray._new(np.roll._implementation(x._array, shift, axis=axis)) def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -51,7 +53,7 @@ def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) See its docstring for more information. """ - return np.squeeze._implementation(x, axis=axis) + return ndarray._array(np.squeeze._implementation(x._array, axis=axis)) def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: """ @@ -59,4 +61,5 @@ def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: See its docstring for more information. """ - return np.stack._implementation(arrays, axis=axis) + arrays = tuple(a._array for a in arrays) + return ndarray._array(np.stack._implementation(arrays, axis=axis)) From 63be085194ddf9d2d8fc32a0ccbe30936c78d870 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 2 Mar 2021 16:10:01 -0700 Subject: [PATCH 040/151] Only allow __bool__, __int__, and __float__ on arrays with shape () --- numpy/_array_api/_array_object.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 23f8ab33313..32a7bc9a8da 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -83,6 +83,9 @@ def __bool__(x: array, /) -> bool: """ Performs the operation __bool__. """ + # Note: This is an error here. + if x._array.shape != (): + raise TypeError("bool is only allowed on arrays with shape ()") res = x._array.__bool__() return res @@ -111,6 +114,9 @@ def __float__(x: array, /) -> float: """ Performs the operation __float__. """ + # Note: This is an error here. + if x._array.shape != (): + raise TypeError("bool is only allowed on arrays with shape ()") res = x._array.__float__() return res @@ -146,6 +152,9 @@ def __int__(x: array, /) -> int: """ Performs the operation __int__. """ + # Note: This is an error here. + if x._array.shape != (): + raise TypeError("bool is only allowed on arrays with shape ()") res = x._array.__int__() return res From 7132764661b01e2f15a66d7c39d74ad4b2d434a9 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 2 Mar 2021 16:29:07 -0700 Subject: [PATCH 041/151] Remove _implementation from the array API functions As discussed at https://mail.python.org/pipermail/numpy-discussion/2021-February/081541.html, _implementation is not as useful for the array API module as previously thought. --- numpy/_array_api/_creation_functions.py | 8 ++++---- numpy/_array_api/_elementwise_functions.py | 2 +- numpy/_array_api/_linear_algebra_functions.py | 10 +++++----- numpy/_array_api/_manipulation_functions.py | 12 ++++++------ numpy/_array_api/_searching_functions.py | 8 ++++---- numpy/_array_api/_set_functions.py | 2 +- numpy/_array_api/_sorting_functions.py | 4 ++-- numpy/_array_api/_statistical_functions.py | 14 +++++++------- numpy/_array_api/_utility_functions.py | 4 ++-- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 888f245582e..5b73c8f5c9c 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -62,7 +62,7 @@ def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.empty_like._implementation(x._array, dtype=dtype)) + return ndarray._new(np.empty_like(x._array, dtype=dtype)) def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -109,7 +109,7 @@ def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dty if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - res = np.full_like._implementation(x._array, fill_value, dtype=dtype) + res = np.full_like(x._array, fill_value, dtype=dtype) if res.dtype not in _all_dtypes: # This will happen if the fill value is not something that NumPy # coerces to one of the acceptable dtypes. @@ -150,7 +150,7 @@ def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[de if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.ones_like._implementation(x._array, dtype=dtype)) + return ndarray._new(np.ones_like(x._array, dtype=dtype)) def zeros(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ @@ -174,4 +174,4 @@ def zeros_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.zeros_like._implementation(x._array, dtype=dtype)) + return ndarray._new(np.zeros_like(x._array, dtype=dtype)) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index b48a38c3dd5..9efe17e839e 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -482,7 +482,7 @@ def round(x: array, /) -> array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in round') - return ndarray._new(np.round._implementation(x._array)) + return ndarray._new(np.round(x._array)) def sign(x: array, /) -> array: """ diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index e23800e0fe4..ec67f9c0b9c 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -18,7 +18,7 @@ def cross(x1: array, x2: array, /, *, axis: int = -1) -> array: See its docstring for more information. """ - return np.cross._implementation(x1, x2, axis=axis) + return np.cross(x1, x2, axis=axis) def det(x: array, /) -> array: """ @@ -35,7 +35,7 @@ def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> See its docstring for more information. """ - return np.diagonal._implementation(x, axis1=axis1, axis2=axis2, offset=offset) + return np.diagonal(x, axis1=axis1, axis2=axis2, offset=offset) # def dot(): # """ @@ -128,7 +128,7 @@ def outer(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.outer._implementation(x1, x2) + return np.outer(x1, x2) # def pinv(): # """ @@ -176,7 +176,7 @@ def trace(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> ar See its docstring for more information. """ - return np.asarray(np.trace._implementation(x, axis1=axis1, axis2=axis2, offset=offset)) + return np.asarray(np.trace(x, axis1=axis1, axis2=axis2, offset=offset)) def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: """ @@ -184,4 +184,4 @@ def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: See its docstring for more information. """ - return np.transpose._implementation(x, axes=axes) + return np.transpose(x, axes=axes) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 413dbb1b1ba..1631a924f70 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -21,7 +21,7 @@ def expand_dims(x: array, axis: int, /) -> array: See its docstring for more information. """ - return ndarray._new(np.expand_dims._implementation(x._array, axis)) + return ndarray._new(np.expand_dims(x._array, axis)) def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -29,7 +29,7 @@ def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> See its docstring for more information. """ - return ndarray._new(np.flip._implementation(x._array, axis=axis)) + return ndarray._new(np.flip(x._array, axis=axis)) def reshape(x: array, shape: Tuple[int, ...], /) -> array: """ @@ -37,7 +37,7 @@ def reshape(x: array, shape: Tuple[int, ...], /) -> array: See its docstring for more information. """ - return ndarray._new(np.reshape._implementation(x._array, shape)) + return ndarray._new(np.reshape(x._array, shape)) def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -45,7 +45,7 @@ def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Unio See its docstring for more information. """ - return ndarray._new(np.roll._implementation(x._array, shift, axis=axis)) + return ndarray._new(np.roll(x._array, shift, axis=axis)) def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ @@ -53,7 +53,7 @@ def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) See its docstring for more information. """ - return ndarray._array(np.squeeze._implementation(x._array, axis=axis)) + return ndarray._array(np.squeeze(x._array, axis=axis)) def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: """ @@ -62,4 +62,4 @@ def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: See its docstring for more information. """ arrays = tuple(a._array for a in arrays) - return ndarray._array(np.stack._implementation(arrays, axis=axis)) + return ndarray._array(np.stack(arrays, axis=axis)) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 77e4710e57d..d5128cca979 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -11,7 +11,7 @@ def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ # Note: this currently fails as np.argmax does not implement keepdims - return np.asarray(np.argmax._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.argmax(x, axis=axis, keepdims=keepdims)) def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: """ @@ -20,7 +20,7 @@ def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ # Note: this currently fails as np.argmin does not implement keepdims - return np.asarray(np.argmin._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.argmin(x, axis=axis, keepdims=keepdims)) def nonzero(x: array, /) -> Tuple[array, ...]: """ @@ -28,7 +28,7 @@ def nonzero(x: array, /) -> Tuple[array, ...]: See its docstring for more information. """ - return np.nonzero._implementation(x) + return np.nonzero(x) def where(condition: array, x1: array, x2: array, /) -> array: """ @@ -36,4 +36,4 @@ def where(condition: array, x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.where._implementation(condition, x1, x2) + return np.where(condition, x1, x2) diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 4dfc215a7bf..91927a3a070 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -10,4 +10,4 @@ def unique(x: array, /, *, return_counts: bool = False, return_index: bool = Fal See its docstring for more information. """ - return np.unique._implementation(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse) + return np.unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 17316b552e1..cddfd1598ae 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -12,7 +12,7 @@ def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bo """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = np.argsort._implementation(x, axis=axis, kind=kind) + res = np.argsort(x, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) return res @@ -25,7 +25,7 @@ def sort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = np.sort._implementation(x, axis=axis, kind=kind) + res = np.sort(x, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) return res diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 79bc125dc86..e62410d01ec 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -5,24 +5,24 @@ import numpy as np def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.max._implementation(x, axis=axis, keepdims=keepdims) + return np.max(x, axis=axis, keepdims=keepdims) def mean(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.mean._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.mean(x, axis=axis, keepdims=keepdims)) def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.min._implementation(x, axis=axis, keepdims=keepdims) + return np.min(x, axis=axis, keepdims=keepdims) def prod(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.prod._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.prod(x, axis=axis, keepdims=keepdims)) def std(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.asarray(np.std._implementation(x, axis=axis, ddof=correction, keepdims=keepdims)) + return np.asarray(np.std(x, axis=axis, ddof=correction, keepdims=keepdims)) def sum(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.sum._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.sum(x, axis=axis, keepdims=keepdims)) def var(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.asarray(np.var._implementation(x, axis=axis, ddof=correction, keepdims=keepdims)) + return np.asarray(np.var(x, axis=axis, ddof=correction, keepdims=keepdims)) diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 7e1d6ec6e9d..51a04dc8bbd 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -10,7 +10,7 @@ def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.asarray(np.all._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.all(x, axis=axis, keepdims=keepdims)) def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: """ @@ -18,4 +18,4 @@ def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.asarray(np.any._implementation(x, axis=axis, keepdims=keepdims)) + return np.asarray(np.any(x, axis=axis, keepdims=keepdims)) From 58c2a996afd13f729ec5d2aed77151c8e799548b Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 2 Mar 2021 16:57:03 -0700 Subject: [PATCH 042/151] Make sure the array API ndarray object cannot wrap an array scalar --- numpy/_array_api/_array_object.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 32a7bc9a8da..b78405860c9 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -19,6 +19,8 @@ from ._types import Optional, PyCapsule, Tuple, Union, array from ._creation_functions import asarray +import numpy as np + class ndarray: # Use a custom constructor instead of __init__, as manually initializing # this class is not supported API. @@ -34,6 +36,10 @@ def _new(cls, x, /): """ obj = super().__new__(cls) + # Note: The spec does not have array scalars, only shape () arrays. + if isinstance(x, np.generic): + # x[...] converts an array scalar to a shape () array. + x = x[...] obj._array = x return obj From cdd6bbcdf260a4d6947901604dc8dd64c864c8d4 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 5 Mar 2021 14:35:35 -0700 Subject: [PATCH 043/151] Support the ndarray object in the remaining array API functions --- numpy/_array_api/_data_type_functions.py | 4 +++- numpy/_array_api/_linear_algebra_functions.py | 17 +++++++++-------- numpy/_array_api/_searching_functions.py | 9 +++++---- numpy/_array_api/_set_functions.py | 3 ++- numpy/_array_api/_sorting_functions.py | 9 +++++---- numpy/_array_api/_statistical_functions.py | 15 ++++++++------- numpy/_array_api/_utility_functions.py | 5 +++-- 7 files changed, 35 insertions(+), 27 deletions(-) diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 18f741ebdc8..9e4dcba953d 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -1,6 +1,8 @@ from __future__ import annotations from ._types import Union, array, dtype +from ._array_object import ndarray + from collections.abc import Sequence import numpy as np @@ -27,4 +29,4 @@ def result_type(*arrays_and_dtypes: Sequence[Union[array, dtype]]) -> dtype: See its docstring for more information. """ - return np.result_type(*arrays_and_dtypes) + return np.result_type(*(a._array if isinstance(a, ndarray) else a for a in arrays_and_dtypes)) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index ec67f9c0b9c..95ed00fd524 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import Literal, Optional, Tuple, Union, array +from ._array_object import ndarray import numpy as np @@ -18,7 +19,7 @@ def cross(x1: array, x2: array, /, *, axis: int = -1) -> array: See its docstring for more information. """ - return np.cross(x1, x2, axis=axis) + return ndarray._new(np.cross(x1._array, x2._array, axis=axis)) def det(x: array, /) -> array: """ @@ -27,7 +28,7 @@ def det(x: array, /) -> array: See its docstring for more information. """ # Note: this function is being imported from a nondefault namespace - return np.linalg.det(x) + return ndarray._new(np.linalg.det(x._array)) def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> array: """ @@ -35,7 +36,7 @@ def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> See its docstring for more information. """ - return np.diagonal(x, axis1=axis1, axis2=axis2, offset=offset) + return ndarray._new(np.diagonal(x._array, axis1=axis1, axis2=axis2, offset=offset)) # def dot(): # """ @@ -76,7 +77,7 @@ def inv(x: array, /) -> array: See its docstring for more information. """ # Note: this function is being imported from a nondefault namespace - return np.linalg.inv(x) + return ndarray._new(np.linalg.inv(x._array)) # def lstsq(): # """ @@ -120,7 +121,7 @@ def norm(x: array, /, *, axis: Optional[Union[int, Tuple[int, int]]] = None, kee if axis == None and x.ndim > 2: x = x.flatten() # Note: this function is being imported from a nondefault namespace - return np.linalg.norm(x, axis=axis, keepdims=keepdims, ord=ord) + return ndarray._new(np.linalg.norm(x._array, axis=axis, keepdims=keepdims, ord=ord)) def outer(x1: array, x2: array, /) -> array: """ @@ -128,7 +129,7 @@ def outer(x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.outer(x1, x2) + return ndarray._new(np.outer(x1._array, x2._array)) # def pinv(): # """ @@ -176,7 +177,7 @@ def trace(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> ar See its docstring for more information. """ - return np.asarray(np.trace(x, axis1=axis1, axis2=axis2, offset=offset)) + return ndarray._new(np.asarray(np.trace(x._array, axis1=axis1, axis2=axis2, offset=offset))) def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: """ @@ -184,4 +185,4 @@ def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: See its docstring for more information. """ - return np.transpose(x, axes=axes) + return ndarray._new(np.transpose(x._array, axes=axes)) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index d5128cca979..44e5b2775a0 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import Tuple, array +from ._array_object import ndarray import numpy as np @@ -11,7 +12,7 @@ def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ # Note: this currently fails as np.argmax does not implement keepdims - return np.asarray(np.argmax(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: """ @@ -20,7 +21,7 @@ def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: See its docstring for more information. """ # Note: this currently fails as np.argmin does not implement keepdims - return np.asarray(np.argmin(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.argmin(x._array, axis=axis, keepdims=keepdims))) def nonzero(x: array, /) -> Tuple[array, ...]: """ @@ -28,7 +29,7 @@ def nonzero(x: array, /) -> Tuple[array, ...]: See its docstring for more information. """ - return np.nonzero(x) + return ndarray._new(np.nonzero(x._array)) def where(condition: array, x1: array, x2: array, /) -> array: """ @@ -36,4 +37,4 @@ def where(condition: array, x1: array, x2: array, /) -> array: See its docstring for more information. """ - return np.where(condition, x1, x2) + return ndarray._new(np.where(condition._array, x1._array, x2._array)) diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 91927a3a070..f5cd6d324a4 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import Tuple, Union, array +from ._array_object import ndarray import numpy as np @@ -10,4 +11,4 @@ def unique(x: array, /, *, return_counts: bool = False, return_index: bool = Fal See its docstring for more information. """ - return np.unique(x, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse) + return ndarray._new(np.unique(x._array, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse)) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index cddfd1598ae..2e054b03a63 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import array +from ._array_object import ndarray import numpy as np @@ -12,10 +13,10 @@ def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bo """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = np.argsort(x, axis=axis, kind=kind) + res = np.argsort(x._array, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) - return res + return ndarray._new(res) def sort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> array: """ @@ -25,7 +26,7 @@ def sort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool """ # Note: this keyword argument is different, and the default is different. kind = 'stable' if stable else 'quicksort' - res = np.sort(x, axis=axis, kind=kind) + res = np.sort(x._array, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) - return res + return ndarray._new(res) diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index e62410d01ec..fa3551248d3 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -1,28 +1,29 @@ from __future__ import annotations from ._types import Optional, Tuple, Union, array +from ._array_object import ndarray import numpy as np def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.max(x, axis=axis, keepdims=keepdims) + return ndarray._new(np.max(x._array, axis=axis, keepdims=keepdims)) def mean(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.mean(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.mean(x._array, axis=axis, keepdims=keepdims))) def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.min(x, axis=axis, keepdims=keepdims) + return ndarray._new(np.min(x._array, axis=axis, keepdims=keepdims)) def prod(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.prod(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.prod(x._array, axis=axis, keepdims=keepdims))) def std(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.asarray(np.std(x, axis=axis, ddof=correction, keepdims=keepdims)) + return ndarray._new(np.asarray(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims))) def sum(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: - return np.asarray(np.sum(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.sum(x._array, axis=axis, keepdims=keepdims))) def var(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: # Note: the keyword argument correction is different here - return np.asarray(np.var(x, axis=axis, ddof=correction, keepdims=keepdims)) + return ndarray._new(np.asarray(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims))) diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 51a04dc8bbd..c4721ad7edc 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._types import Optional, Tuple, Union, array +from ._array_object import ndarray import numpy as np @@ -10,7 +11,7 @@ def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.asarray(np.all(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.all(x._array, axis=axis, keepdims=keepdims))) def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: """ @@ -18,4 +19,4 @@ def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return np.asarray(np.any(x, axis=axis, keepdims=keepdims)) + return ndarray._new(np.asarray(np.any(x._array, axis=axis, keepdims=keepdims))) From 1ccbe680e24f2d2254ee4cd5053bb10859b22d1d Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 15:47:07 -0700 Subject: [PATCH 044/151] Only allow indices that are required by the spec in the array API namespace The private function _validate_indices describes the cases that are disallowed. This functionality should be tested (it isn't yet), as the array API test suite will only test the cases that are allowed, not that non-required cases are rejected. --- numpy/_array_api/_array_object.py | 115 ++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index b78405860c9..64ce740f065 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -15,9 +15,11 @@ from __future__ import annotations +import operator from enum import IntEnum from ._types import Optional, PyCapsule, Tuple, Union, array from ._creation_functions import asarray +from ._dtypes import _boolean_dtypes, _integer_dtypes import numpy as np @@ -140,10 +142,120 @@ def __ge__(x1: array, x2: array, /) -> array: res = x1._array.__ge__(asarray(x2)._array) return x1.__class__._new(res) + # Note: A large fraction of allowed indices are disallowed here (see the + # docstring below) + @staticmethod + def _validate_index(key, shape): + """ + Validate an index according to the array API. + + The array API specification only requires a subset of indices that are + supported by NumPy. This function will reject any index that is + allowed by NumPy but not required by the array API specification. We + always raise ``IndexError`` on such indices (the spec does not require + any specific behavior on them, but this makes the NumPy array API + namespace a minimal implementation of the spec). + + This function either raises IndexError if the index ``key`` is + invalid, or a new key to be used in place of ``key`` in indexing. It + only raises ``IndexError`` on indices that are not already rejected by + NumPy, as NumPy will already raise the appropriate error on such + indices. ``shape`` may be None, in which case, only cases that are + independent of the array shape are checked. + + The following cases are allowed by NumPy, but not specified by the array + API specification: + + - The start and stop of a slice may not be out of bounds. In + particular, for a slice ``i:j:k`` on an axis of size ``n``, only the + following are allowed: + + - ``i`` or ``j`` omitted (``None``). + - ``-n <= i <= max(0, n - 1)``. + - For ``k > 0`` or ``k`` omitted (``None``), ``-n <= j <= n``. + - For ``k < 0``, ``-n - 1 <= j <= max(0, n - 1)``. + + - Boolean array indices are not allowed as part of a larger tuple + index. + + - Integer array indices are not allowed (with the exception of shape + () arrays, which are treated the same as scalars). + + Additionally, it should be noted that indices that would return a + scalar in NumPy will return a shape () array. Array scalars are not allowed + in the specification, only shape () arrays. This is done in the + ``ndarray._new`` constructor, not this function. + + """ + if isinstance(key, slice): + if shape is None: + return key + if shape == (): + return key + size = shape[0] + # Ensure invalid slice entries are passed through. + if key.start is not None: + try: + operator.index(key.start) + except TypeError: + return key + if not (-size <= key.start <= max(0, size - 1)): + raise IndexError("Slices with out-of-bounds start are not allowed in the array API namespace") + if key.stop is not None: + try: + operator.index(key.stop) + except TypeError: + return key + step = 1 if key.step is None else key.step + if (step > 0 and not (-size <= key.stop <= size) + or step < 0 and not (-size - 1 <= key.stop <= max(0, size - 1))): + raise IndexError("Slices with out-of-bounds stop are not allowed in the array API namespace") + return key + + elif isinstance(key, tuple): + key = tuple(ndarray._validate_index(idx, None) for idx in key) + + for idx in key: + if isinstance(idx, np.ndarray) and idx.dtype in _boolean_dtypes or isinstance(idx, (bool, np.bool_)): + if len(key) == 1: + return key + raise IndexError("Boolean array indices combined with other indices are not allowed in the array API namespace") + + if shape is None: + return key + n_ellipsis = key.count(...) + if n_ellipsis > 1: + return key + ellipsis_i = key.index(...) if n_ellipsis else len(key) + + for idx, size in list(zip(key[:ellipsis_i], shape)) + list(zip(key[:ellipsis_i:-1], shape[:ellipsis_i:-1])): + ndarray._validate_index(idx, (size,)) + return key + elif isinstance(key, bool): + return key + elif isinstance(key, ndarray): + if key.dtype in _integer_dtypes: + if key.shape != (): + raise IndexError("Integer array indices with shape != () are not allowed in the array API namespace") + return key._array + elif key is Ellipsis: + return key + elif key is None: + raise IndexError("newaxis indices are not allowed in the array API namespace") + try: + return operator.index(key) + except TypeError: + # Note: This also omits boolean arrays that are not already in + # ndarray() form, like a list of booleans. + raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") + def __getitem__(x: array, key: Union[int, slice, Tuple[Union[int, slice], ...], array], /) -> array: """ Performs the operation __getitem__. """ + # Note: Only indices required by the spec are allowed. See the + # docstring of _validate_index + key = x._validate_index(key, x.shape) res = x._array.__getitem__(key) return x.__class__._new(res) @@ -266,6 +378,9 @@ def __setitem__(x, key, value, /): """ Performs the operation __setitem__. """ + # Note: Only indices required by the spec are allowed. See the + # docstring of _validate_index + key = x._validate_index(key, x.shape) res = x._array.__setitem__(key, asarray(value)._array) return x.__class__._new(res) From be45fa10e993c858e559b9fb0556e18a5e355595 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 15:54:53 -0700 Subject: [PATCH 045/151] Update the state of the array API in the __init__.py docstring --- numpy/_array_api/__init__.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 43b2d4ce302..880deb6137f 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -58,20 +58,32 @@ guaranteed to give a comprehensive coverage of the spec. Therefore, those reviewing this submodule should refer to the standard documents themselves. +- There is a custom array object, numpy._array_api.ndarray, which is returned + by all functions in this module. All functions in the array API namespace + implicitly assume that they will only receive this object as input. The only + way to create instances of this object is to use one of the array creation + functions. It does not have a public constructor on the object itself. The + object is a small wrapper Python class around numpy.ndarray. The main + purpose of it is to restrict the namespace of the array object to only those + methods that are required by the spec, as well as to limit/change certain + behavior that differs in the spec. In particular: + + - Indexing: Only a subset of indices supported by NumPy are required by the + spec. The ndarray object restricts indexing to only allow those types of + indices that are required by the spec. See the docstring of the + numpy._array_api.ndarray._validate_indices helper function for more + information. + + - Type promotion: Some type promotion rules are different in the spec. In + particular, the spec does not have any value-based casing. Note that the + code to correct the type promotion rules on numpy._array_api.ndarray is + not yet implemented. + - All functions include type annotations, corresponding to those given in the spec (see _types.py for definitions of the types 'array', 'device', and 'dtype'). These do not currently fully pass mypy due to some limitations in mypy. -- The array object is not modified at all. That means that functions return - np.ndarray, which has methods and attributes that aren't part of the spec. - Modifying/subclassing ndarray for the purposes of the array API namespace - was considered too complex for this initial implementation. - -- All functions that would otherwise accept array-like input have been wrapped - to only accept ndarray (with the exception of methods on the array object, - which are not modified). - - All places where the implementations in this submodule are known to deviate from their corresponding functions in NumPy are marked with "# Note" comments. Reviewers should make note of these comments. From 0ac7de9a670517a46ee82670d3a790dafbb6071c Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 17:01:41 -0700 Subject: [PATCH 046/151] Implement __array_namespace__ on the array API ndarray object --- numpy/_array_api/_array_object.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 64ce740f065..84cbf3527a0 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -87,6 +87,12 @@ def __and__(x1: array, x2: array, /) -> array: res = x1._array.__and__(asarray(x2)._array) return x1.__class__._new(res) + def __array_namespace__(self, /, *, api_version=None): + if api_version is not None: + raise ValueError("Unrecognized array API version") + from numpy import _array_api + return _array_api + def __bool__(x: array, /) -> bool: """ Performs the operation __bool__. From 9e50716df9c7b4b59a11fda8fc99835f070cc152 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 17:18:46 -0700 Subject: [PATCH 047/151] Use 'self' and 'other' for the array API ndarray method parameter names --- numpy/_array_api/_array_object.py | 354 +++++++++++++++--------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 84cbf3527a0..2471940170c 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -52,40 +52,40 @@ def __new__(cls, *args, **kwargs): # These functions are not required by the spec, but are implemented for # the sake of usability. - def __str__(x: array, /) -> str: + def __str__(self: array, /) -> str: """ Performs the operation __str__. """ - return x._array.__str__().replace('array', 'ndarray') + return self._array.__str__().replace('array', 'ndarray') - def __repr__(x: array, /) -> str: + def __repr__(self: array, /) -> str: """ Performs the operation __repr__. """ - return x._array.__repr__().replace('array', 'ndarray') + return self._array.__repr__().replace('array', 'ndarray') # Everything below this is required by the spec. - def __abs__(x: array, /) -> array: + def __abs__(self: array, /) -> array: """ Performs the operation __abs__. """ - res = x._array.__abs__() - return x.__class__._new(res) + res = self._array.__abs__() + return self.__class__._new(res) - def __add__(x1: array, x2: array, /) -> array: + def __add__(self: array, other: array, /) -> array: """ Performs the operation __add__. """ - res = x1._array.__add__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__add__(asarray(other)._array) + return self.__class__._new(res) - def __and__(x1: array, x2: array, /) -> array: + def __and__(self: array, other: array, /) -> array: """ Performs the operation __and__. """ - res = x1._array.__and__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__and__(asarray(other)._array) + return self.__class__._new(res) def __array_namespace__(self, /, *, api_version=None): if api_version is not None: @@ -93,60 +93,60 @@ def __array_namespace__(self, /, *, api_version=None): from numpy import _array_api return _array_api - def __bool__(x: array, /) -> bool: + def __bool__(self: array, /) -> bool: """ Performs the operation __bool__. """ # Note: This is an error here. - if x._array.shape != (): + if self._array.shape != (): raise TypeError("bool is only allowed on arrays with shape ()") - res = x._array.__bool__() + res = self._array.__bool__() return res - def __dlpack__(x: array, /, *, stream: Optional[int] = None) -> PyCapsule: + def __dlpack__(self: array, /, *, stream: Optional[int] = None) -> PyCapsule: """ Performs the operation __dlpack__. """ - res = x._array.__dlpack__(stream=None) - return x.__class__._new(res) + res = self._array.__dlpack__(stream=None) + return self.__class__._new(res) - def __dlpack_device__(x: array, /) -> Tuple[IntEnum, int]: + def __dlpack_device__(self: array, /) -> Tuple[IntEnum, int]: """ Performs the operation __dlpack_device__. """ - res = x._array.__dlpack_device__() - return x.__class__._new(res) + res = self._array.__dlpack_device__() + return self.__class__._new(res) - def __eq__(x1: array, x2: array, /) -> array: + def __eq__(self: array, other: array, /) -> array: """ Performs the operation __eq__. """ - res = x1._array.__eq__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__eq__(asarray(other)._array) + return self.__class__._new(res) - def __float__(x: array, /) -> float: + def __float__(self: array, /) -> float: """ Performs the operation __float__. """ # Note: This is an error here. - if x._array.shape != (): + if self._array.shape != (): raise TypeError("bool is only allowed on arrays with shape ()") - res = x._array.__float__() + res = self._array.__float__() return res - def __floordiv__(x1: array, x2: array, /) -> array: + def __floordiv__(self: array, other: array, /) -> array: """ Performs the operation __floordiv__. """ - res = x1._array.__floordiv__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__floordiv__(asarray(other)._array) + return self.__class__._new(res) - def __ge__(x1: array, x2: array, /) -> array: + def __ge__(self: array, other: array, /) -> array: """ Performs the operation __ge__. """ - res = x1._array.__ge__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ge__(asarray(other)._array) + return self.__class__._new(res) # Note: A large fraction of allowed indices are disallowed here (see the # docstring below) @@ -255,343 +255,343 @@ def _validate_index(key, shape): # ndarray() form, like a list of booleans. raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") - def __getitem__(x: array, key: Union[int, slice, Tuple[Union[int, slice], ...], array], /) -> array: + def __getitem__(self: array, key: Union[int, slice, Tuple[Union[int, slice], ...], array], /) -> array: """ Performs the operation __getitem__. """ # Note: Only indices required by the spec are allowed. See the # docstring of _validate_index - key = x._validate_index(key, x.shape) - res = x._array.__getitem__(key) - return x.__class__._new(res) + key = self._validate_index(key, self.shape) + res = self._array.__getitem__(key) + return self.__class__._new(res) - def __gt__(x1: array, x2: array, /) -> array: + def __gt__(self: array, other: array, /) -> array: """ Performs the operation __gt__. """ - res = x1._array.__gt__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__gt__(asarray(other)._array) + return self.__class__._new(res) - def __int__(x: array, /) -> int: + def __int__(self: array, /) -> int: """ Performs the operation __int__. """ # Note: This is an error here. - if x._array.shape != (): + if self._array.shape != (): raise TypeError("bool is only allowed on arrays with shape ()") - res = x._array.__int__() + res = self._array.__int__() return res - def __invert__(x: array, /) -> array: + def __invert__(self: array, /) -> array: """ Performs the operation __invert__. """ - res = x._array.__invert__() - return x.__class__._new(res) + res = self._array.__invert__() + return self.__class__._new(res) - def __le__(x1: array, x2: array, /) -> array: + def __le__(self: array, other: array, /) -> array: """ Performs the operation __le__. """ - res = x1._array.__le__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__le__(asarray(other)._array) + return self.__class__._new(res) - def __len__(x, /): + def __len__(self, /): """ Performs the operation __len__. """ - res = x._array.__len__() - return x.__class__._new(res) + res = self._array.__len__() + return self.__class__._new(res) - def __lshift__(x1: array, x2: array, /) -> array: + def __lshift__(self: array, other: array, /) -> array: """ Performs the operation __lshift__. """ - res = x1._array.__lshift__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__lshift__(asarray(other)._array) + return self.__class__._new(res) - def __lt__(x1: array, x2: array, /) -> array: + def __lt__(self: array, other: array, /) -> array: """ Performs the operation __lt__. """ - res = x1._array.__lt__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__lt__(asarray(other)._array) + return self.__class__._new(res) - def __matmul__(x1: array, x2: array, /) -> array: + def __matmul__(self: array, other: array, /) -> array: """ Performs the operation __matmul__. """ - res = x1._array.__matmul__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__matmul__(asarray(other)._array) + return self.__class__._new(res) - def __mod__(x1: array, x2: array, /) -> array: + def __mod__(self: array, other: array, /) -> array: """ Performs the operation __mod__. """ - res = x1._array.__mod__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__mod__(asarray(other)._array) + return self.__class__._new(res) - def __mul__(x1: array, x2: array, /) -> array: + def __mul__(self: array, other: array, /) -> array: """ Performs the operation __mul__. """ - res = x1._array.__mul__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__mul__(asarray(other)._array) + return self.__class__._new(res) - def __ne__(x1: array, x2: array, /) -> array: + def __ne__(self: array, other: array, /) -> array: """ Performs the operation __ne__. """ - res = x1._array.__ne__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ne__(asarray(other)._array) + return self.__class__._new(res) - def __neg__(x: array, /) -> array: + def __neg__(self: array, /) -> array: """ Performs the operation __neg__. """ - res = x._array.__neg__() - return x.__class__._new(res) + res = self._array.__neg__() + return self.__class__._new(res) - def __or__(x1: array, x2: array, /) -> array: + def __or__(self: array, other: array, /) -> array: """ Performs the operation __or__. """ - res = x1._array.__or__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__or__(asarray(other)._array) + return self.__class__._new(res) - def __pos__(x: array, /) -> array: + def __pos__(self: array, /) -> array: """ Performs the operation __pos__. """ - res = x._array.__pos__() - return x.__class__._new(res) + res = self._array.__pos__() + return self.__class__._new(res) - def __pow__(x1: array, x2: array, /) -> array: + def __pow__(self: array, other: array, /) -> array: """ Performs the operation __pow__. """ - res = x1._array.__pow__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__pow__(asarray(other)._array) + return self.__class__._new(res) - def __rshift__(x1: array, x2: array, /) -> array: + def __rshift__(self: array, other: array, /) -> array: """ Performs the operation __rshift__. """ - res = x1._array.__rshift__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rshift__(asarray(other)._array) + return self.__class__._new(res) - def __setitem__(x, key, value, /): + def __setitem__(self, key, value, /): """ Performs the operation __setitem__. """ # Note: Only indices required by the spec are allowed. See the # docstring of _validate_index - key = x._validate_index(key, x.shape) - res = x._array.__setitem__(key, asarray(value)._array) - return x.__class__._new(res) + key = self._validate_index(key, self.shape) + res = self._array.__setitem__(key, asarray(value)._array) + return self.__class__._new(res) - def __sub__(x1: array, x2: array, /) -> array: + def __sub__(self: array, other: array, /) -> array: """ Performs the operation __sub__. """ - res = x1._array.__sub__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__sub__(asarray(other)._array) + return self.__class__._new(res) - def __truediv__(x1: array, x2: array, /) -> array: + def __truediv__(self: array, other: array, /) -> array: """ Performs the operation __truediv__. """ - res = x1._array.__truediv__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__truediv__(asarray(other)._array) + return self.__class__._new(res) - def __xor__(x1: array, x2: array, /) -> array: + def __xor__(self: array, other: array, /) -> array: """ Performs the operation __xor__. """ - res = x1._array.__xor__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__xor__(asarray(other)._array) + return self.__class__._new(res) - def __iadd__(x1: array, x2: array, /) -> array: + def __iadd__(self: array, other: array, /) -> array: """ Performs the operation __iadd__. """ - res = x1._array.__iadd__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__iadd__(asarray(other)._array) + return self.__class__._new(res) - def __radd__(x1: array, x2: array, /) -> array: + def __radd__(self: array, other: array, /) -> array: """ Performs the operation __radd__. """ - res = x1._array.__radd__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__radd__(asarray(other)._array) + return self.__class__._new(res) - def __iand__(x1: array, x2: array, /) -> array: + def __iand__(self: array, other: array, /) -> array: """ Performs the operation __iand__. """ - res = x1._array.__iand__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__iand__(asarray(other)._array) + return self.__class__._new(res) - def __rand__(x1: array, x2: array, /) -> array: + def __rand__(self: array, other: array, /) -> array: """ Performs the operation __rand__. """ - res = x1._array.__rand__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rand__(asarray(other)._array) + return self.__class__._new(res) - def __ifloordiv__(x1: array, x2: array, /) -> array: + def __ifloordiv__(self: array, other: array, /) -> array: """ Performs the operation __ifloordiv__. """ - res = x1._array.__ifloordiv__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ifloordiv__(asarray(other)._array) + return self.__class__._new(res) - def __rfloordiv__(x1: array, x2: array, /) -> array: + def __rfloordiv__(self: array, other: array, /) -> array: """ Performs the operation __rfloordiv__. """ - res = x1._array.__rfloordiv__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rfloordiv__(asarray(other)._array) + return self.__class__._new(res) - def __ilshift__(x1: array, x2: array, /) -> array: + def __ilshift__(self: array, other: array, /) -> array: """ Performs the operation __ilshift__. """ - res = x1._array.__ilshift__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ilshift__(asarray(other)._array) + return self.__class__._new(res) - def __rlshift__(x1: array, x2: array, /) -> array: + def __rlshift__(self: array, other: array, /) -> array: """ Performs the operation __rlshift__. """ - res = x1._array.__rlshift__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rlshift__(asarray(other)._array) + return self.__class__._new(res) - def __imatmul__(x1: array, x2: array, /) -> array: + def __imatmul__(self: array, other: array, /) -> array: """ Performs the operation __imatmul__. """ - res = x1._array.__imatmul__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__imatmul__(asarray(other)._array) + return self.__class__._new(res) - def __rmatmul__(x1: array, x2: array, /) -> array: + def __rmatmul__(self: array, other: array, /) -> array: """ Performs the operation __rmatmul__. """ - res = x1._array.__rmatmul__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rmatmul__(asarray(other)._array) + return self.__class__._new(res) - def __imod__(x1: array, x2: array, /) -> array: + def __imod__(self: array, other: array, /) -> array: """ Performs the operation __imod__. """ - res = x1._array.__imod__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__imod__(asarray(other)._array) + return self.__class__._new(res) - def __rmod__(x1: array, x2: array, /) -> array: + def __rmod__(self: array, other: array, /) -> array: """ Performs the operation __rmod__. """ - res = x1._array.__rmod__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rmod__(asarray(other)._array) + return self.__class__._new(res) - def __imul__(x1: array, x2: array, /) -> array: + def __imul__(self: array, other: array, /) -> array: """ Performs the operation __imul__. """ - res = x1._array.__imul__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__imul__(asarray(other)._array) + return self.__class__._new(res) - def __rmul__(x1: array, x2: array, /) -> array: + def __rmul__(self: array, other: array, /) -> array: """ Performs the operation __rmul__. """ - res = x1._array.__rmul__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rmul__(asarray(other)._array) + return self.__class__._new(res) - def __ior__(x1: array, x2: array, /) -> array: + def __ior__(self: array, other: array, /) -> array: """ Performs the operation __ior__. """ - res = x1._array.__ior__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ior__(asarray(other)._array) + return self.__class__._new(res) - def __ror__(x1: array, x2: array, /) -> array: + def __ror__(self: array, other: array, /) -> array: """ Performs the operation __ror__. """ - res = x1._array.__ror__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ror__(asarray(other)._array) + return self.__class__._new(res) - def __ipow__(x1: array, x2: array, /) -> array: + def __ipow__(self: array, other: array, /) -> array: """ Performs the operation __ipow__. """ - res = x1._array.__ipow__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ipow__(asarray(other)._array) + return self.__class__._new(res) - def __rpow__(x1: array, x2: array, /) -> array: + def __rpow__(self: array, other: array, /) -> array: """ Performs the operation __rpow__. """ - res = x1._array.__rpow__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rpow__(asarray(other)._array) + return self.__class__._new(res) - def __irshift__(x1: array, x2: array, /) -> array: + def __irshift__(self: array, other: array, /) -> array: """ Performs the operation __irshift__. """ - res = x1._array.__irshift__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__irshift__(asarray(other)._array) + return self.__class__._new(res) - def __rrshift__(x1: array, x2: array, /) -> array: + def __rrshift__(self: array, other: array, /) -> array: """ Performs the operation __rrshift__. """ - res = x1._array.__rrshift__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rrshift__(asarray(other)._array) + return self.__class__._new(res) - def __isub__(x1: array, x2: array, /) -> array: + def __isub__(self: array, other: array, /) -> array: """ Performs the operation __isub__. """ - res = x1._array.__isub__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__isub__(asarray(other)._array) + return self.__class__._new(res) - def __rsub__(x1: array, x2: array, /) -> array: + def __rsub__(self: array, other: array, /) -> array: """ Performs the operation __rsub__. """ - res = x1._array.__rsub__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rsub__(asarray(other)._array) + return self.__class__._new(res) - def __itruediv__(x1: array, x2: array, /) -> array: + def __itruediv__(self: array, other: array, /) -> array: """ Performs the operation __itruediv__. """ - res = x1._array.__itruediv__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__itruediv__(asarray(other)._array) + return self.__class__._new(res) - def __rtruediv__(x1: array, x2: array, /) -> array: + def __rtruediv__(self: array, other: array, /) -> array: """ Performs the operation __rtruediv__. """ - res = x1._array.__rtruediv__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rtruediv__(asarray(other)._array) + return self.__class__._new(res) - def __ixor__(x1: array, x2: array, /) -> array: + def __ixor__(self: array, other: array, /) -> array: """ Performs the operation __ixor__. """ - res = x1._array.__ixor__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__ixor__(asarray(other)._array) + return self.__class__._new(res) - def __rxor__(x1: array, x2: array, /) -> array: + def __rxor__(self: array, other: array, /) -> array: """ Performs the operation __rxor__. """ - res = x1._array.__rxor__(asarray(x2)._array) - return x1.__class__._new(res) + res = self._array.__rxor__(asarray(other)._array) + return self.__class__._new(res) @property def dtype(self): From 6c17d4bfd080c00082efa60891212f95b2500c18 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 17:19:42 -0700 Subject: [PATCH 048/151] Update the array API namespace __init__.py docstring with todos --- numpy/_array_api/__init__.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 880deb6137f..ebbe0bb9137 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -65,8 +65,8 @@ functions. It does not have a public constructor on the object itself. The object is a small wrapper Python class around numpy.ndarray. The main purpose of it is to restrict the namespace of the array object to only those - methods that are required by the spec, as well as to limit/change certain - behavior that differs in the spec. In particular: + dtypes and only those methods that are required by the spec, as well as to + limit/change certain behavior that differs in the spec. In particular: - Indexing: Only a subset of indices supported by NumPy are required by the spec. The ndarray object restricts indexing to only allow those types of @@ -75,7 +75,7 @@ information. - Type promotion: Some type promotion rules are different in the spec. In - particular, the spec does not have any value-based casing. Note that the + particular, the spec does not have any value-based casting. Note that the code to correct the type promotion rules on numpy._array_api.ndarray is not yet implemented. @@ -84,10 +84,30 @@ 'dtype'). These do not currently fully pass mypy due to some limitations in mypy. +- The wrapper functions in this module do not do any type checking for things + that would be impossible without leaving the _array_api namespace. + - All places where the implementations in this submodule are known to deviate from their corresponding functions in NumPy are marked with "# Note" comments. Reviewers should make note of these comments. +Still TODO in this module are: + +- Implement the spec type promotion rules on the ndarray object. + +- Disable NumPy warnings in the API functions. + +- Implement keepdims on argmin and argmax. + +- Device support and DLPack support are not yet implemented. These require + support in NumPy itself first. + +- The a non-default value for the `copy` keyword argument is not yet + implemented on asarray. This requires support in numpy.asarray() first. + +- Some functions are not yet fully tested in the array API test suite, and may + require updates that are not yet known until the tests are written. + """ __all__ = [] From 48a2d8c39bb58611307122f55da6f0e99cf86086 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 17:19:50 -0700 Subject: [PATCH 049/151] Add a small docstring to the array API ndarray object --- numpy/_array_api/_array_object.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 2471940170c..e9ea6ef4574 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -24,6 +24,20 @@ import numpy as np class ndarray: + """ + ndarray object for the array API namespace. + + See the docstring of :py:obj:`np.ndarray ` for more + information. + + This is a wrapper around numpy.ndarray that restricts the usage to only + those things that are required by the array API namespace. Note, + attributes on this object that start with a single underscore are not part + of the API specification and should only be used internally. This object + should not be constructed directly. Rather, use one of the creation + functions, such as asarray(). + + """ # Use a custom constructor instead of __init__, as manually initializing # this class is not supported API. @classmethod From 8671a368cff459d5a00c5b43d330d084e2f6ed3d Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 9 Mar 2021 17:20:56 -0700 Subject: [PATCH 050/151] Use the array API types for the array API type annotations --- numpy/_array_api/_types.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index 3800b71565b..32d03e2a771 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -11,12 +11,13 @@ from typing import Literal, Optional, Tuple, Union, TypeVar -import numpy as np +from . import (ndarray, int8, int16, int32, int64, uint8, uint16, uint32, + uint64, float32, float64) -array = np.ndarray +array = ndarray device = TypeVar('device') -dtype = Literal[np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, - np.uint32, np.uint64, np.float32, np.float64] +dtype = Literal[int8, int16, int32, int64, uint8, uint16, + uint32, uint64, float32, float64] SupportsDLPack = TypeVar('SupportsDLPack') SupportsBufferProtocol = TypeVar('SupportsBufferProtocol') PyCapsule = TypeVar('PyCapsule') From da5dc954ea5e5a1c4fc43240f4ddfec051d71845 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 11 Mar 2021 16:44:39 -0700 Subject: [PATCH 051/151] Only return the same array in asarray if the dtype is the same --- numpy/_array_api/_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 5b73c8f5c9c..cee379f5939 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -21,7 +21,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su if copy is not None: # Note: copy is not yet implemented in np.asarray raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") - if isinstance(obj, ndarray): + if isinstance(obj, ndarray) and (dtype is None or obj.dtype == dtype): return obj res = np.asarray(obj, dtype=dtype) if res.dtype not in _all_dtypes: From e42ae01029caf63e28b7533d4f7fd03ac0244066 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 11 Mar 2021 16:44:53 -0700 Subject: [PATCH 052/151] Use more robust code for converting an array scalar to a shape () array --- numpy/_array_api/_array_object.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index e9ea6ef4574..371361de9f4 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -54,8 +54,10 @@ def _new(cls, x, /): obj = super().__new__(cls) # Note: The spec does not have array scalars, only shape () arrays. if isinstance(x, np.generic): - # x[...] converts an array scalar to a shape () array. - x = x[...] + # Convert the array scalar to a shape () array + xa = np.empty((), x.dtype) + xa[()] = x + x = xa obj._array = x return obj From ed05662905f83864a937fa67b7f762cda1277df2 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 17 Mar 2021 19:28:18 -0600 Subject: [PATCH 053/151] Fix circular imports from types --- numpy/_array_api/_array_object.py | 5 ++++- numpy/_array_api/_creation_functions.py | 7 +++++-- numpy/_array_api/_data_type_functions.py | 5 ++++- numpy/_array_api/_elementwise_functions.py | 5 ++++- numpy/_array_api/_linear_algebra_functions.py | 5 ++++- numpy/_array_api/_manipulation_functions.py | 5 ++++- numpy/_array_api/_searching_functions.py | 5 ++++- numpy/_array_api/_set_functions.py | 5 ++++- numpy/_array_api/_sorting_functions.py | 5 ++++- numpy/_array_api/_statistical_functions.py | 5 ++++- numpy/_array_api/_utility_functions.py | 5 ++++- 11 files changed, 45 insertions(+), 12 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 371361de9f4..4b11a0ca0e2 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -17,10 +17,13 @@ import operator from enum import IntEnum -from ._types import Optional, PyCapsule, Tuple, Union, array from ._creation_functions import asarray from ._dtypes import _boolean_dtypes, _integer_dtypes +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Optional, PyCapsule, Tuple, Union, array + import numpy as np class ndarray: diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index cee379f5939..e9ef983fd3c 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -1,7 +1,10 @@ from __future__ import annotations -from ._types import (Optional, SupportsDLPack, SupportsBufferProtocol, Tuple, - Union, array, device, dtype) + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import (Optional, SupportsDLPack, SupportsBufferProtocol, Tuple, + Union, array, device, dtype) from ._dtypes import _all_dtypes import numpy as np diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 9e4dcba953d..0488ba9155b 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Union, array, dtype from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Union, array, dtype + from collections.abc import Sequence import numpy as np diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 9efe17e839e..cd2e8661f99 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -2,9 +2,12 @@ from ._dtypes import (_boolean_dtypes, _floating_dtypes, _integer_dtypes, _integer_or_boolean_dtypes, _numeric_dtypes) -from ._types import array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import array + import numpy as np def abs(x: array, /) -> array: diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index 95ed00fd524..f57fe292aa8 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Literal, Optional, Tuple, Union, array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Literal, Optional, Tuple, Union, array + import numpy as np # def cholesky(): diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 1631a924f70..4e5ca0728fc 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Optional, Tuple, Union, array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Optional, Tuple, Union, array + import numpy as np def concat(arrays: Tuple[array], /, *, axis: Optional[int] = 0) -> array: diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 44e5b2775a0..9a5d583bcf7 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Tuple, array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Tuple, array + import numpy as np def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index f5cd6d324a4..025a27d809a 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Tuple, Union, array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Tuple, Union, array + import numpy as np def unique(x: array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False) -> Union[array, Tuple[array, ...]]: diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 2e054b03a63..6e87bd90e1e 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import array + import numpy as np def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> array: diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index fa3551248d3..26afd735454 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Optional, Tuple, Union, array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Optional, Tuple, Union, array + import numpy as np def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index c4721ad7edc..e280b57853d 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -1,8 +1,11 @@ from __future__ import annotations -from ._types import Optional, Tuple, Union, array from ._array_object import ndarray +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from ._types import Optional, Tuple, Union, array + import numpy as np def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: From 8968bc31944eb2af214b591d38eca3b0cea56f4b Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 18 Mar 2021 15:20:24 -0600 Subject: [PATCH 054/151] bitwise_left_shift and bitwise_right_shift should return the dtype of the first argument --- numpy/_array_api/_elementwise_functions.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index cd2e8661f99..a8af04c62bd 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -123,10 +123,13 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: See its docstring for more information. """ + # Note: the function name is different here if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') - # Note: the function name is different here - return ndarray._new(np.left_shift(x1._array, x2._array)) + # Note: The spec requires the return dtype of bitwise_left_shift to be the + # same as the first argument. np.left_shift() returns a type that is the + # type promotion of the two input types. + return ndarray._new(np.left_shift(x1._array, x2._array).astype(x1.dtype)) def bitwise_invert(x: array, /) -> array: """ @@ -155,10 +158,13 @@ def bitwise_right_shift(x1: array, x2: array, /) -> array: See its docstring for more information. """ + # Note: the function name is different here if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') - # Note: the function name is different here - return ndarray._new(np.right_shift(x1._array, x2._array)) + # Note: The spec requires the return dtype of bitwise_left_shift to be the + # same as the first argument. np.left_shift() returns a type that is the + # type promotion of the two input types. + return ndarray._new(np.right_shift(x1._array, x2._array).astype(x1.dtype)) def bitwise_xor(x1: array, x2: array, /) -> array: """ From 479c8a24121465bbb9e0e193dc2da39cd08bdfe4 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 18 Mar 2021 15:26:50 -0600 Subject: [PATCH 055/151] bitwise_left_shift and bitwise_right_shift are only defined for x2 >= 0 --- numpy/_array_api/_elementwise_functions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index a8af04c62bd..aa48f440c2b 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -126,6 +126,9 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: # Note: the function name is different here if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') + # Note: bitwise_left_shift is only defined for x2 nonnegative. + if np.any(x2._array < 0): + raise ValueError('bitwise_left_shift(x1, x2) is only defined for x2 >= 0') # Note: The spec requires the return dtype of bitwise_left_shift to be the # same as the first argument. np.left_shift() returns a type that is the # type promotion of the two input types. @@ -161,6 +164,9 @@ def bitwise_right_shift(x1: array, x2: array, /) -> array: # Note: the function name is different here if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') + # Note: bitwise_right_shift is only defined for x2 nonnegative. + if np.any(x2._array < 0): + raise ValueError('bitwise_right_shift(x1, x2) is only defined for x2 >= 0') # Note: The spec requires the return dtype of bitwise_left_shift to be the # same as the first argument. np.left_shift() returns a type that is the # type promotion of the two input types. From 7ce435c610fcd7fee01da9d9e7ff5c1ab4ae6ef6 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 30 Mar 2021 13:53:11 -0600 Subject: [PATCH 056/151] Update some annotations updated from the spec --- numpy/_array_api/_array_object.py | 4 ++-- numpy/_array_api/_data_type_functions.py | 4 ++-- numpy/_array_api/_manipulation_functions.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 4b11a0ca0e2..ad0cbc71ee1 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -106,7 +106,7 @@ def __and__(self: array, other: array, /) -> array: res = self._array.__and__(asarray(other)._array) return self.__class__._new(res) - def __array_namespace__(self, /, *, api_version=None): + def __array_namespace__(self: array, /, *, api_version: Optional[str] = None) -> object: if api_version is not None: raise ValueError("Unrecognized array API version") from numpy import _array_api @@ -274,7 +274,7 @@ def _validate_index(key, shape): # ndarray() form, like a list of booleans. raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") - def __getitem__(self: array, key: Union[int, slice, Tuple[Union[int, slice], ...], array], /) -> array: + def __getitem__(self: array, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array], /) -> array: """ Performs the operation __getitem__. """ diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 0488ba9155b..d4816a41f7d 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -10,7 +10,7 @@ import numpy as np -def finfo(type: Union[dtype, array], /) -> finfo: +def finfo(type: Union[dtype, array], /) -> finfo_object: """ Array API compatible wrapper for :py:func:`np.finfo `. @@ -18,7 +18,7 @@ def finfo(type: Union[dtype, array], /) -> finfo: """ return np.finfo(type) -def iinfo(type: Union[dtype, array], /) -> iinfo: +def iinfo(type: Union[dtype, array], /) -> iinfo_object: """ Array API compatible wrapper for :py:func:`np.iinfo `. diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 4e5ca0728fc..033ed23a0ab 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -8,7 +8,7 @@ import numpy as np -def concat(arrays: Tuple[array], /, *, axis: Optional[int] = 0) -> array: +def concat(arrays: Tuple[array, ...], /, *, axis: Optional[int] = 0) -> array: """ Array API compatible wrapper for :py:func:`np.concatenate `. @@ -58,7 +58,7 @@ def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) """ return ndarray._array(np.squeeze(x._array, axis=axis)) -def stack(arrays: Tuple[array], /, *, axis: int = 0) -> array: +def stack(arrays: Tuple[array, ...], /, *, axis: int = 0) -> array: """ Array API compatible wrapper for :py:func:`np.stack `. From 9fe4fc7ff7f477fc4aaad850f8d1841beb2924bc Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 31 Mar 2021 16:34:23 -0600 Subject: [PATCH 057/151] Make the array API follow the spec Python scalar promotion rules --- numpy/_array_api/_array_object.py | 129 +++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index ad0cbc71ee1..c5acb5d1d59 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -18,7 +18,7 @@ import operator from enum import IntEnum from ._creation_functions import asarray -from ._dtypes import _boolean_dtypes, _integer_dtypes +from ._dtypes import _boolean_dtypes, _integer_dtypes, _floating_dtypes from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -83,6 +83,37 @@ def __repr__(self: array, /) -> str: """ return self._array.__repr__().replace('array', 'ndarray') + # Helper function to match the type promotion rules in the spec + def _promote_scalar(self, scalar): + """ + Returns a promoted version of a Python scalar appropiate for use with + operations on self. + + This may raise an OverflowError in cases where the scalar is an + integer that is too large to fit in a NumPy integer dtype, or + TypeError when the scalar type is incompatible with the dtype of self. + + Note: this helper function returns a NumPy array (NOT a NumPy array + API ndarray). + """ + if isinstance(scalar, bool): + if self.dtype not in _boolean_dtypes: + raise TypeError("Python bool scalars can only be promoted with bool arrays") + elif isinstance(scalar, int): + if self.dtype in _boolean_dtypes: + raise TypeError("Python int scalars cannot be promoted with bool arrays") + elif isinstance(scalar, float): + if self.dtype not in _floating_dtypes: + raise TypeError("Python float scalars can only be promoted with floating-point arrays.") + else: + raise TypeError("'scalar' must be a Python scalar") + + # Note: the spec only specifies integer-dtype/int promotion + # behavior for integers within the bounds of the integer dtype. + # Outside of those bounds we use the default NumPy behavior (either + # cast or raise OverflowError). + return np.array(scalar, self.dtype) + # Everything below this is required by the spec. def __abs__(self: array, /) -> array: @@ -96,6 +127,8 @@ def __add__(self: array, other: array, /) -> array: """ Performs the operation __add__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__add__(asarray(other)._array) return self.__class__._new(res) @@ -103,6 +136,8 @@ def __and__(self: array, other: array, /) -> array: """ Performs the operation __and__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__and__(asarray(other)._array) return self.__class__._new(res) @@ -140,6 +175,8 @@ def __eq__(self: array, other: array, /) -> array: """ Performs the operation __eq__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__eq__(asarray(other)._array) return self.__class__._new(res) @@ -157,6 +194,8 @@ def __floordiv__(self: array, other: array, /) -> array: """ Performs the operation __floordiv__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__floordiv__(asarray(other)._array) return self.__class__._new(res) @@ -164,6 +203,8 @@ def __ge__(self: array, other: array, /) -> array: """ Performs the operation __ge__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ge__(asarray(other)._array) return self.__class__._new(res) @@ -288,6 +329,8 @@ def __gt__(self: array, other: array, /) -> array: """ Performs the operation __gt__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__gt__(asarray(other)._array) return self.__class__._new(res) @@ -312,6 +355,8 @@ def __le__(self: array, other: array, /) -> array: """ Performs the operation __le__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__le__(asarray(other)._array) return self.__class__._new(res) @@ -326,6 +371,8 @@ def __lshift__(self: array, other: array, /) -> array: """ Performs the operation __lshift__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__lshift__(asarray(other)._array) return self.__class__._new(res) @@ -333,6 +380,8 @@ def __lt__(self: array, other: array, /) -> array: """ Performs the operation __lt__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__lt__(asarray(other)._array) return self.__class__._new(res) @@ -340,6 +389,10 @@ def __matmul__(self: array, other: array, /) -> array: """ Performs the operation __matmul__. """ + if isinstance(other, (int, float, bool)): + # matmul is not defined for scalars, but without this, we may get + # the wrong error message from asarray. + other = self._promote_scalar(other) res = self._array.__matmul__(asarray(other)._array) return self.__class__._new(res) @@ -347,6 +400,8 @@ def __mod__(self: array, other: array, /) -> array: """ Performs the operation __mod__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__mod__(asarray(other)._array) return self.__class__._new(res) @@ -354,6 +409,8 @@ def __mul__(self: array, other: array, /) -> array: """ Performs the operation __mul__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__mul__(asarray(other)._array) return self.__class__._new(res) @@ -361,6 +418,8 @@ def __ne__(self: array, other: array, /) -> array: """ Performs the operation __ne__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ne__(asarray(other)._array) return self.__class__._new(res) @@ -375,6 +434,8 @@ def __or__(self: array, other: array, /) -> array: """ Performs the operation __or__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__or__(asarray(other)._array) return self.__class__._new(res) @@ -389,6 +450,8 @@ def __pow__(self: array, other: array, /) -> array: """ Performs the operation __pow__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__pow__(asarray(other)._array) return self.__class__._new(res) @@ -396,6 +459,8 @@ def __rshift__(self: array, other: array, /) -> array: """ Performs the operation __rshift__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rshift__(asarray(other)._array) return self.__class__._new(res) @@ -413,6 +478,8 @@ def __sub__(self: array, other: array, /) -> array: """ Performs the operation __sub__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__sub__(asarray(other)._array) return self.__class__._new(res) @@ -420,6 +487,8 @@ def __truediv__(self: array, other: array, /) -> array: """ Performs the operation __truediv__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__truediv__(asarray(other)._array) return self.__class__._new(res) @@ -427,6 +496,8 @@ def __xor__(self: array, other: array, /) -> array: """ Performs the operation __xor__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__xor__(asarray(other)._array) return self.__class__._new(res) @@ -434,6 +505,8 @@ def __iadd__(self: array, other: array, /) -> array: """ Performs the operation __iadd__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__iadd__(asarray(other)._array) return self.__class__._new(res) @@ -441,6 +514,8 @@ def __radd__(self: array, other: array, /) -> array: """ Performs the operation __radd__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__radd__(asarray(other)._array) return self.__class__._new(res) @@ -448,6 +523,8 @@ def __iand__(self: array, other: array, /) -> array: """ Performs the operation __iand__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__iand__(asarray(other)._array) return self.__class__._new(res) @@ -455,6 +532,8 @@ def __rand__(self: array, other: array, /) -> array: """ Performs the operation __rand__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rand__(asarray(other)._array) return self.__class__._new(res) @@ -462,6 +541,8 @@ def __ifloordiv__(self: array, other: array, /) -> array: """ Performs the operation __ifloordiv__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ifloordiv__(asarray(other)._array) return self.__class__._new(res) @@ -469,6 +550,8 @@ def __rfloordiv__(self: array, other: array, /) -> array: """ Performs the operation __rfloordiv__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rfloordiv__(asarray(other)._array) return self.__class__._new(res) @@ -476,6 +559,8 @@ def __ilshift__(self: array, other: array, /) -> array: """ Performs the operation __ilshift__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ilshift__(asarray(other)._array) return self.__class__._new(res) @@ -483,6 +568,8 @@ def __rlshift__(self: array, other: array, /) -> array: """ Performs the operation __rlshift__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rlshift__(asarray(other)._array) return self.__class__._new(res) @@ -490,6 +577,10 @@ def __imatmul__(self: array, other: array, /) -> array: """ Performs the operation __imatmul__. """ + if isinstance(other, (int, float, bool)): + # matmul is not defined for scalars, but without this, we may get + # the wrong error message from asarray. + other = self._promote_scalar(other) res = self._array.__imatmul__(asarray(other)._array) return self.__class__._new(res) @@ -497,6 +588,10 @@ def __rmatmul__(self: array, other: array, /) -> array: """ Performs the operation __rmatmul__. """ + if isinstance(other, (int, float, bool)): + # matmul is not defined for scalars, but without this, we may get + # the wrong error message from asarray. + other = self._promote_scalar(other) res = self._array.__rmatmul__(asarray(other)._array) return self.__class__._new(res) @@ -504,6 +599,8 @@ def __imod__(self: array, other: array, /) -> array: """ Performs the operation __imod__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__imod__(asarray(other)._array) return self.__class__._new(res) @@ -511,6 +608,8 @@ def __rmod__(self: array, other: array, /) -> array: """ Performs the operation __rmod__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rmod__(asarray(other)._array) return self.__class__._new(res) @@ -518,6 +617,8 @@ def __imul__(self: array, other: array, /) -> array: """ Performs the operation __imul__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__imul__(asarray(other)._array) return self.__class__._new(res) @@ -525,6 +626,8 @@ def __rmul__(self: array, other: array, /) -> array: """ Performs the operation __rmul__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rmul__(asarray(other)._array) return self.__class__._new(res) @@ -532,6 +635,8 @@ def __ior__(self: array, other: array, /) -> array: """ Performs the operation __ior__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ior__(asarray(other)._array) return self.__class__._new(res) @@ -539,6 +644,8 @@ def __ror__(self: array, other: array, /) -> array: """ Performs the operation __ror__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ror__(asarray(other)._array) return self.__class__._new(res) @@ -546,6 +653,8 @@ def __ipow__(self: array, other: array, /) -> array: """ Performs the operation __ipow__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ipow__(asarray(other)._array) return self.__class__._new(res) @@ -553,6 +662,8 @@ def __rpow__(self: array, other: array, /) -> array: """ Performs the operation __rpow__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rpow__(asarray(other)._array) return self.__class__._new(res) @@ -560,6 +671,8 @@ def __irshift__(self: array, other: array, /) -> array: """ Performs the operation __irshift__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__irshift__(asarray(other)._array) return self.__class__._new(res) @@ -567,6 +680,8 @@ def __rrshift__(self: array, other: array, /) -> array: """ Performs the operation __rrshift__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rrshift__(asarray(other)._array) return self.__class__._new(res) @@ -574,6 +689,8 @@ def __isub__(self: array, other: array, /) -> array: """ Performs the operation __isub__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__isub__(asarray(other)._array) return self.__class__._new(res) @@ -581,6 +698,8 @@ def __rsub__(self: array, other: array, /) -> array: """ Performs the operation __rsub__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rsub__(asarray(other)._array) return self.__class__._new(res) @@ -588,6 +707,8 @@ def __itruediv__(self: array, other: array, /) -> array: """ Performs the operation __itruediv__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__itruediv__(asarray(other)._array) return self.__class__._new(res) @@ -595,6 +716,8 @@ def __rtruediv__(self: array, other: array, /) -> array: """ Performs the operation __rtruediv__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rtruediv__(asarray(other)._array) return self.__class__._new(res) @@ -602,6 +725,8 @@ def __ixor__(self: array, other: array, /) -> array: """ Performs the operation __ixor__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__ixor__(asarray(other)._array) return self.__class__._new(res) @@ -609,6 +734,8 @@ def __rxor__(self: array, other: array, /) -> array: """ Performs the operation __rxor__. """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) res = self._array.__rxor__(asarray(other)._array) return self.__class__._new(res) From fb5c69775f516f06fcf6e79e0762f09e6e8cb907 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 31 Mar 2021 17:38:46 -0600 Subject: [PATCH 058/151] Give a better error message in the array API asarray for out of bounds integers Without this the error message would be a confusing message about object arrays not being supported. --- numpy/_array_api/_creation_functions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index e9ef983fd3c..107f3c31366 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -26,6 +26,10 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") if isinstance(obj, ndarray) and (dtype is None or obj.dtype == dtype): return obj + if isinstance(obj, int) and (obj > 2**64 or obj < -2**63): + # Give a better error message in this case. NumPy would convert this + # to an object array. + raise OverflowError("Integer out of bounds for array dtypes") res = np.asarray(obj, dtype=dtype) if res.dtype not in _all_dtypes: raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") From b75a135751e4b38f144027678d1ddc74ee4d50fc Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 13 Apr 2021 17:05:44 -0600 Subject: [PATCH 059/151] Fix int bounds checking in asarray() to only happen when dtype=None --- numpy/_array_api/_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 107f3c31366..003b10afb32 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -26,7 +26,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") if isinstance(obj, ndarray) and (dtype is None or obj.dtype == dtype): return obj - if isinstance(obj, int) and (obj > 2**64 or obj < -2**63): + if dtype is None and isinstance(obj, int) and (obj > 2**64 or obj < -2**63): # Give a better error message in this case. NumPy would convert this # to an object array. raise OverflowError("Integer out of bounds for array dtypes") From d40d2bcfbe01678479fe741ab8b0ff5e431e0329 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 13 Apr 2021 18:11:09 -0600 Subject: [PATCH 060/151] Fix ceil() and floor() in the array API to always return the same dtype --- numpy/_array_api/_elementwise_functions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index aa48f440c2b..3ca71b53ec3 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -190,6 +190,9 @@ def ceil(x: array, /) -> array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in ceil') + if x.dtype in _integer_dtypes: + # Note: The return dtype of ceil is the same as the input + return x return ndarray._new(np.ceil(x._array)) def cos(x: array, /) -> array: @@ -258,6 +261,9 @@ def floor(x: array, /) -> array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in floor') + if x.dtype in _integer_dtypes: + # Note: The return dtype of floor is the same as the input + return x return ndarray._new(np.floor(x._array)) def floor_divide(x1: array, x2: array, /) -> array: From 844fcd39692da676a7204c6cc7feea428ba49609 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 14 Apr 2021 17:07:00 -0600 Subject: [PATCH 061/151] Move function name change notes to before the def line --- numpy/_array_api/_elementwise_functions.py | 22 ++++++++++----------- numpy/_array_api/_manipulation_functions.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 3ca71b53ec3..5ee33f60ff1 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -20,6 +20,7 @@ def abs(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in abs') return ndarray._new(np.abs(x._array)) +# Note: the function name is different here def acos(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arccos `. @@ -28,9 +29,9 @@ def acos(x: array, /) -> array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in acos') - # Note: the function name is different here return ndarray._new(np.arccos(x._array)) +# Note: the function name is different here def acosh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arccosh `. @@ -39,7 +40,6 @@ def acosh(x: array, /) -> array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in acosh') - # Note: the function name is different here return ndarray._new(np.arccosh(x._array)) def add(x1: array, x2: array, /) -> array: @@ -52,6 +52,7 @@ def add(x1: array, x2: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in add') return ndarray._new(np.add(x1._array, x2._array)) +# Note: the function name is different here def asin(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arcsin `. @@ -60,9 +61,9 @@ def asin(x: array, /) -> array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in asin') - # Note: the function name is different here return ndarray._new(np.arcsin(x._array)) +# Note: the function name is different here def asinh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arcsinh `. @@ -71,9 +72,9 @@ def asinh(x: array, /) -> array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in asinh') - # Note: the function name is different here return ndarray._new(np.arcsinh(x._array)) +# Note: the function name is different here def atan(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctan `. @@ -82,9 +83,9 @@ def atan(x: array, /) -> array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atan') - # Note: the function name is different here return ndarray._new(np.arctan(x._array)) +# Note: the function name is different here def atan2(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctan2 `. @@ -93,9 +94,9 @@ def atan2(x1: array, x2: array, /) -> array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atan2') - # Note: the function name is different here return ndarray._new(np.arctan2(x1._array, x2._array)) +# Note: the function name is different here def atanh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctanh `. @@ -104,7 +105,6 @@ def atanh(x: array, /) -> array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atanh') - # Note: the function name is different here return ndarray._new(np.arctanh(x._array)) def bitwise_and(x1: array, x2: array, /) -> array: @@ -117,13 +117,13 @@ def bitwise_and(x1: array, x2: array, /) -> array: raise TypeError('Only integer_or_boolean dtypes are allowed in bitwise_and') return ndarray._new(np.bitwise_and(x1._array, x2._array)) +# Note: the function name is different here def bitwise_left_shift(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.left_shift `. See its docstring for more information. """ - # Note: the function name is different here if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') # Note: bitwise_left_shift is only defined for x2 nonnegative. @@ -134,6 +134,7 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: # type promotion of the two input types. return ndarray._new(np.left_shift(x1._array, x2._array).astype(x1.dtype)) +# Note: the function name is different here def bitwise_invert(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.invert `. @@ -142,7 +143,6 @@ def bitwise_invert(x: array, /) -> array: """ if x.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_invert') - # Note: the function name is different here return ndarray._new(np.invert(x._array)) def bitwise_or(x1: array, x2: array, /) -> array: @@ -155,13 +155,13 @@ def bitwise_or(x1: array, x2: array, /) -> array: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_or') return ndarray._new(np.bitwise_or(x1._array, x2._array)) +# Note: the function name is different here def bitwise_right_shift(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.right_shift `. See its docstring for more information. """ - # Note: the function name is different here if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') # Note: bitwise_right_shift is only defined for x2 nonnegative. @@ -474,6 +474,7 @@ def positive(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in positive') return ndarray._new(np.positive(x._array)) +# Note: the function name is different here def pow(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.power `. @@ -482,7 +483,6 @@ def pow(x1: array, x2: array, /) -> array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in pow') - # Note: the function name is different here return ndarray._new(np.power(x1._array, x2._array)) def remainder(x1: array, x2: array, /) -> array: diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 033ed23a0ab..6ac7be02f85 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -8,6 +8,7 @@ import numpy as np +# Note: the function name is different here def concat(arrays: Tuple[array, ...], /, *, axis: Optional[int] = 0) -> array: """ Array API compatible wrapper for :py:func:`np.concatenate `. @@ -15,7 +16,6 @@ def concat(arrays: Tuple[array, ...], /, *, axis: Optional[int] = 0) -> array: See its docstring for more information. """ arrays = tuple(a._array for a in arrays) - # Note: the function name is different here return ndarray._new(np.concatenate(arrays, axis=axis)) def expand_dims(x: array, axis: int, /) -> array: From 9af1cc60edd4fdbb7e9c18d124e639a44ce420c7 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Apr 2021 15:31:33 -0600 Subject: [PATCH 062/151] Use dtype objects instead of classes in the array API The array API does not require any methods or behaviors on dtype objects, other than that they be literals that can be compared for equality and passed to dtype keywords in functions. Since dtype objects are already used by the dtype attribute of ndarray, this makes it consistent, so that func(dtype=).dtype will give exactly back, which will be the same thing as numpy._array_api.. This also fixes an issue in the array API test suite due to the fact that dtype classes and objects are not equal as dictionary keys. --- numpy/_array_api/_dtypes.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index d33ae1fce87..c874763ddb3 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -1,6 +1,19 @@ -from .. import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64 +import numpy as np + +# Note: we use dtype objects instead of dtype classes. The spec does not +# require any behavior on dtypes other than equality. +int8 = np.dtype('int8') +int16 = np.dtype('int16') +int32 = np.dtype('int32') +int64 = np.dtype('int64') +uint8 = np.dtype('uint8') +uint16 = np.dtype('uint16') +uint32 = np.dtype('uint32') +uint64 = np.dtype('uint64') +float32 = np.dtype('float32') +float64 = np.dtype('float64') # Note: This name is changed -from .. import bool_ as bool +bool = np.dtype('bool') _all_dtypes = [int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool] From 6c196f540429aa0869e8fb66917a5e76447d2c02 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Apr 2021 15:39:14 -0600 Subject: [PATCH 063/151] Fix type promotion consistency for the array API elementwise functions and operators NumPy's type promotion behavior deviates from the spec, which says that type promotion should work independently of shapes or values, in cases where one array is 0-d and the other is not. A helper function is added that works around this issue by adding a dimension to the 0-d array before passing it to the NumPy function. This function is used in elementwise functions and operators. It may still need to be applied to other functions in the namespace. Additionally, this fixes: - The shift operators (<< and >>) should always return the same dtype as the first argument. - NumPy's __pow__ does not type promote the two arguments, so we use the array API pow() in ndarray.__pow__, which does. - The internal _promote_scalar helper function was changed to return an array API ndarray object, as this is simpler with the inclusion of the new _normalize_two_args helper in the operators. --- numpy/_array_api/_array_object.py | 180 +++++++++++++++------ numpy/_array_api/_elementwise_functions.py | 26 ++- 2 files changed, 152 insertions(+), 54 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index c5acb5d1d59..d1aa8d3fb59 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -86,15 +86,12 @@ def __repr__(self: array, /) -> str: # Helper function to match the type promotion rules in the spec def _promote_scalar(self, scalar): """ - Returns a promoted version of a Python scalar appropiate for use with + Returns a promoted version of a Python scalar appropriate for use with operations on self. This may raise an OverflowError in cases where the scalar is an integer that is too large to fit in a NumPy integer dtype, or TypeError when the scalar type is incompatible with the dtype of self. - - Note: this helper function returns a NumPy array (NOT a NumPy array - API ndarray). """ if isinstance(scalar, bool): if self.dtype not in _boolean_dtypes: @@ -112,9 +109,38 @@ def _promote_scalar(self, scalar): # behavior for integers within the bounds of the integer dtype. # Outside of those bounds we use the default NumPy behavior (either # cast or raise OverflowError). - return np.array(scalar, self.dtype) + return ndarray._new(np.array(scalar, self.dtype)) + + @staticmethod + def _normalize_two_args(x1, x2): + """ + Normalize inputs to two arg functions to fix type promotion rules + + NumPy deviates from the spec type promotion rules in cases where one + argument is 0-dimensional and the other is not. For example: + + >>> import numpy as np + >>> a = np.array([1.0], dtype=np.float32) + >>> b = np.array(1.0, dtype=np.float64) + >>> np.add(a, b) # The spec says this should be float64 + array([2.], dtype=float32) - # Everything below this is required by the spec. + To fix this, we add a dimension to the 0-dimension array before passing it + through. This works because a dimension would be added anyway from + broadcasting, so the resulting shape is the same, but this prevents NumPy + from not promoting the dtype. + """ + if x1.shape == () and x2.shape != (): + # The _array[None] workaround was chosen because it is relatively + # performant. broadcast_to(x1._array, x2.shape) is much slower. We + # could also manually type promote x2, but that is more complicated + # and about the same performance as this. + x1 = ndarray._new(x1._array[None]) + elif x2.shape == () and x1.shape != (): + x2 = ndarray._new(x2._array[None]) + return (x1, x2) + + # Everything below this line is required by the spec. def __abs__(self: array, /) -> array: """ @@ -129,7 +155,8 @@ def __add__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__add__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__add__(other._array) return self.__class__._new(res) def __and__(self: array, other: array, /) -> array: @@ -138,7 +165,8 @@ def __and__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__and__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__and__(other._array) return self.__class__._new(res) def __array_namespace__(self: array, /, *, api_version: Optional[str] = None) -> object: @@ -177,7 +205,8 @@ def __eq__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__eq__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__eq__(other._array) return self.__class__._new(res) def __float__(self: array, /) -> float: @@ -196,7 +225,8 @@ def __floordiv__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__floordiv__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__floordiv__(other._array) return self.__class__._new(res) def __ge__(self: array, other: array, /) -> array: @@ -205,7 +235,8 @@ def __ge__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ge__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__ge__(other._array) return self.__class__._new(res) # Note: A large fraction of allowed indices are disallowed here (see the @@ -331,7 +362,8 @@ def __gt__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__gt__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__gt__(other._array) return self.__class__._new(res) def __int__(self: array, /) -> int: @@ -357,7 +389,8 @@ def __le__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__le__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__le__(other._array) return self.__class__._new(res) def __len__(self, /): @@ -373,7 +406,11 @@ def __lshift__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__lshift__(asarray(other)._array) + # Note: The spec requires the return dtype of bitwise_left_shift, and + # hence also __lshift__, to be the same as the first argument. + # np.ndarray.__lshift__ returns a type that is the type promotion of + # the two input types. + res = self._array.__lshift__(other._array).astype(self.dtype) return self.__class__._new(res) def __lt__(self: array, other: array, /) -> array: @@ -382,7 +419,8 @@ def __lt__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__lt__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__lt__(other._array) return self.__class__._new(res) def __matmul__(self: array, other: array, /) -> array: @@ -393,7 +431,7 @@ def __matmul__(self: array, other: array, /) -> array: # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. other = self._promote_scalar(other) - res = self._array.__matmul__(asarray(other)._array) + res = self._array.__matmul__(other._array) return self.__class__._new(res) def __mod__(self: array, other: array, /) -> array: @@ -402,7 +440,8 @@ def __mod__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__mod__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__mod__(other._array) return self.__class__._new(res) def __mul__(self: array, other: array, /) -> array: @@ -411,7 +450,8 @@ def __mul__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__mul__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__mul__(other._array) return self.__class__._new(res) def __ne__(self: array, other: array, /) -> array: @@ -420,7 +460,8 @@ def __ne__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ne__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__ne__(other._array) return self.__class__._new(res) def __neg__(self: array, /) -> array: @@ -436,7 +477,8 @@ def __or__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__or__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__or__(other._array) return self.__class__._new(res) def __pos__(self: array, /) -> array: @@ -450,10 +492,13 @@ def __pow__(self: array, other: array, /) -> array: """ Performs the operation __pow__. """ + from ._elementwise_functions import pow + if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__pow__(asarray(other)._array) - return self.__class__._new(res) + # Note: NumPy's __pow__ does not follow type promotion rules for 0-d + # arrays, so we use pow() here instead. + return pow(self, other) def __rshift__(self: array, other: array, /) -> array: """ @@ -461,7 +506,11 @@ def __rshift__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rshift__(asarray(other)._array) + # Note: The spec requires the return dtype of bitwise_right_shift, and + # hence also __rshift__, to be the same as the first argument. + # np.ndarray.__rshift__ returns a type that is the type promotion of + # the two input types. + res = self._array.__rshift__(other._array).astype(self.dtype) return self.__class__._new(res) def __setitem__(self, key, value, /): @@ -480,7 +529,8 @@ def __sub__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__sub__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__sub__(other._array) return self.__class__._new(res) def __truediv__(self: array, other: array, /) -> array: @@ -489,7 +539,8 @@ def __truediv__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__truediv__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__truediv__(other._array) return self.__class__._new(res) def __xor__(self: array, other: array, /) -> array: @@ -498,7 +549,8 @@ def __xor__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__xor__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__xor__(other._array) return self.__class__._new(res) def __iadd__(self: array, other: array, /) -> array: @@ -507,7 +559,9 @@ def __iadd__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__iadd__(asarray(other)._array) + res = self._array.__iadd__(other._array) + if res.dtype != self.dtype: + raise RuntimeError return self.__class__._new(res) def __radd__(self: array, other: array, /) -> array: @@ -516,7 +570,8 @@ def __radd__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__radd__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__radd__(other._array) return self.__class__._new(res) def __iand__(self: array, other: array, /) -> array: @@ -525,7 +580,7 @@ def __iand__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__iand__(asarray(other)._array) + res = self._array.__iand__(other._array) return self.__class__._new(res) def __rand__(self: array, other: array, /) -> array: @@ -534,7 +589,8 @@ def __rand__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rand__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rand__(other._array) return self.__class__._new(res) def __ifloordiv__(self: array, other: array, /) -> array: @@ -543,7 +599,7 @@ def __ifloordiv__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ifloordiv__(asarray(other)._array) + res = self._array.__ifloordiv__(other._array) return self.__class__._new(res) def __rfloordiv__(self: array, other: array, /) -> array: @@ -552,7 +608,8 @@ def __rfloordiv__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rfloordiv__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rfloordiv__(other._array) return self.__class__._new(res) def __ilshift__(self: array, other: array, /) -> array: @@ -561,7 +618,7 @@ def __ilshift__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ilshift__(asarray(other)._array) + res = self._array.__ilshift__(other._array) return self.__class__._new(res) def __rlshift__(self: array, other: array, /) -> array: @@ -570,7 +627,11 @@ def __rlshift__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rlshift__(asarray(other)._array) + # Note: The spec requires the return dtype of bitwise_left_shift, and + # hence also __lshift__, to be the same as the first argument. + # np.ndarray.__lshift__ returns a type that is the type promotion of + # the two input types. + res = self._array.__rlshift__(other._array).astype(other.dtype) return self.__class__._new(res) def __imatmul__(self: array, other: array, /) -> array: @@ -581,7 +642,7 @@ def __imatmul__(self: array, other: array, /) -> array: # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. other = self._promote_scalar(other) - res = self._array.__imatmul__(asarray(other)._array) + res = self._array.__imatmul__(other._array) return self.__class__._new(res) def __rmatmul__(self: array, other: array, /) -> array: @@ -592,7 +653,7 @@ def __rmatmul__(self: array, other: array, /) -> array: # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. other = self._promote_scalar(other) - res = self._array.__rmatmul__(asarray(other)._array) + res = self._array.__rmatmul__(other._array) return self.__class__._new(res) def __imod__(self: array, other: array, /) -> array: @@ -601,7 +662,7 @@ def __imod__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__imod__(asarray(other)._array) + res = self._array.__imod__(other._array) return self.__class__._new(res) def __rmod__(self: array, other: array, /) -> array: @@ -610,7 +671,8 @@ def __rmod__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rmod__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rmod__(other._array) return self.__class__._new(res) def __imul__(self: array, other: array, /) -> array: @@ -619,7 +681,7 @@ def __imul__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__imul__(asarray(other)._array) + res = self._array.__imul__(other._array) return self.__class__._new(res) def __rmul__(self: array, other: array, /) -> array: @@ -628,7 +690,8 @@ def __rmul__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rmul__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rmul__(other._array) return self.__class__._new(res) def __ior__(self: array, other: array, /) -> array: @@ -637,7 +700,7 @@ def __ior__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ior__(asarray(other)._array) + res = self._array.__ior__(other._array) return self.__class__._new(res) def __ror__(self: array, other: array, /) -> array: @@ -646,7 +709,8 @@ def __ror__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ror__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__ror__(other._array) return self.__class__._new(res) def __ipow__(self: array, other: array, /) -> array: @@ -655,17 +719,20 @@ def __ipow__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ipow__(asarray(other)._array) + res = self._array.__ipow__(other._array) return self.__class__._new(res) def __rpow__(self: array, other: array, /) -> array: """ Performs the operation __rpow__. """ + from ._elementwise_functions import pow + if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rpow__(asarray(other)._array) - return self.__class__._new(res) + # Note: NumPy's __pow__ does not follow the spec type promotion rules + # for 0-d arrays, so we use pow() here instead. + return pow(other, self) def __irshift__(self: array, other: array, /) -> array: """ @@ -673,7 +740,7 @@ def __irshift__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__irshift__(asarray(other)._array) + res = self._array.__irshift__(other._array) return self.__class__._new(res) def __rrshift__(self: array, other: array, /) -> array: @@ -682,7 +749,11 @@ def __rrshift__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rrshift__(asarray(other)._array) + # Note: The spec requires the return dtype of bitwise_right_shift, and + # hence also __rshift__, to be the same as the first argument. + # np.ndarray.__rshift__ returns a type that is the type promotion of + # the two input types. + res = self._array.__rrshift__(other._array).astype(other.dtype) return self.__class__._new(res) def __isub__(self: array, other: array, /) -> array: @@ -691,7 +762,7 @@ def __isub__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__isub__(asarray(other)._array) + res = self._array.__isub__(other._array) return self.__class__._new(res) def __rsub__(self: array, other: array, /) -> array: @@ -700,7 +771,8 @@ def __rsub__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rsub__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rsub__(other._array) return self.__class__._new(res) def __itruediv__(self: array, other: array, /) -> array: @@ -709,7 +781,7 @@ def __itruediv__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__itruediv__(asarray(other)._array) + res = self._array.__itruediv__(other._array) return self.__class__._new(res) def __rtruediv__(self: array, other: array, /) -> array: @@ -718,7 +790,8 @@ def __rtruediv__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rtruediv__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rtruediv__(other._array) return self.__class__._new(res) def __ixor__(self: array, other: array, /) -> array: @@ -727,7 +800,7 @@ def __ixor__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ixor__(asarray(other)._array) + res = self._array.__ixor__(other._array) return self.__class__._new(res) def __rxor__(self: array, other: array, /) -> array: @@ -736,7 +809,8 @@ def __rxor__(self: array, other: array, /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__rxor__(asarray(other)._array) + self, other = self._normalize_two_args(self, other) + res = self._array.__rxor__(other._array) return self.__class__._new(res) @property diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 5ee33f60ff1..cb855da1261 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -1,7 +1,8 @@ from __future__ import annotations from ._dtypes import (_boolean_dtypes, _floating_dtypes, - _integer_dtypes, _integer_or_boolean_dtypes, _numeric_dtypes) + _integer_dtypes, _integer_or_boolean_dtypes, + _numeric_dtypes) from ._array_object import ndarray from typing import TYPE_CHECKING @@ -50,6 +51,7 @@ def add(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in add') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.add(x1._array, x2._array)) # Note: the function name is different here @@ -94,6 +96,7 @@ def atan2(x1: array, x2: array, /) -> array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atan2') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.arctan2(x1._array, x2._array)) # Note: the function name is different here @@ -115,6 +118,7 @@ def bitwise_and(x1: array, x2: array, /) -> array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer_or_boolean dtypes are allowed in bitwise_and') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.bitwise_and(x1._array, x2._array)) # Note: the function name is different here @@ -126,6 +130,7 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') + x1, x2 = ndarray._normalize_two_args(x1, x2) # Note: bitwise_left_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): raise ValueError('bitwise_left_shift(x1, x2) is only defined for x2 >= 0') @@ -153,6 +158,7 @@ def bitwise_or(x1: array, x2: array, /) -> array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_or') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.bitwise_or(x1._array, x2._array)) # Note: the function name is different here @@ -164,6 +170,7 @@ def bitwise_right_shift(x1: array, x2: array, /) -> array: """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') + x1, x2 = ndarray._normalize_two_args(x1, x2) # Note: bitwise_right_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): raise ValueError('bitwise_right_shift(x1, x2) is only defined for x2 >= 0') @@ -180,6 +187,7 @@ def bitwise_xor(x1: array, x2: array, /) -> array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_xor') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.bitwise_xor(x1._array, x2._array)) def ceil(x: array, /) -> array: @@ -223,6 +231,7 @@ def divide(x1: array, x2: array, /) -> array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in divide') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.divide(x1._array, x2._array)) def equal(x1: array, x2: array, /) -> array: @@ -231,6 +240,7 @@ def equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.equal(x1._array, x2._array)) def exp(x: array, /) -> array: @@ -274,6 +284,7 @@ def floor_divide(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in floor_divide') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.floor_divide(x1._array, x2._array)) def greater(x1: array, x2: array, /) -> array: @@ -284,6 +295,7 @@ def greater(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in greater') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.greater(x1._array, x2._array)) def greater_equal(x1: array, x2: array, /) -> array: @@ -294,6 +306,7 @@ def greater_equal(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in greater_equal') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.greater_equal(x1._array, x2._array)) def isfinite(x: array, /) -> array: @@ -334,6 +347,7 @@ def less(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in less') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.less(x1._array, x2._array)) def less_equal(x1: array, x2: array, /) -> array: @@ -344,6 +358,7 @@ def less_equal(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in less_equal') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.less_equal(x1._array, x2._array)) def log(x: array, /) -> array: @@ -394,6 +409,7 @@ def logaddexp(x1: array, x2: array) -> array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in logaddexp') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logaddexp(x1._array, x2._array)) def logical_and(x1: array, x2: array, /) -> array: @@ -404,6 +420,7 @@ def logical_and(x1: array, x2: array, /) -> array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_and') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logical_and(x1._array, x2._array)) def logical_not(x: array, /) -> array: @@ -424,6 +441,7 @@ def logical_or(x1: array, x2: array, /) -> array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_or') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logical_or(x1._array, x2._array)) def logical_xor(x1: array, x2: array, /) -> array: @@ -434,6 +452,7 @@ def logical_xor(x1: array, x2: array, /) -> array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_xor') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logical_xor(x1._array, x2._array)) def multiply(x1: array, x2: array, /) -> array: @@ -444,6 +463,7 @@ def multiply(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in multiply') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.multiply(x1._array, x2._array)) def negative(x: array, /) -> array: @@ -462,6 +482,7 @@ def not_equal(x1: array, x2: array, /) -> array: See its docstring for more information. """ + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.not_equal(x1._array, x2._array)) def positive(x: array, /) -> array: @@ -483,6 +504,7 @@ def pow(x1: array, x2: array, /) -> array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in pow') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.power(x1._array, x2._array)) def remainder(x1: array, x2: array, /) -> array: @@ -493,6 +515,7 @@ def remainder(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in remainder') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.remainder(x1._array, x2._array)) def round(x: array, /) -> array: @@ -563,6 +586,7 @@ def subtract(x1: array, x2: array, /) -> array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in subtract') + x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.subtract(x1._array, x2._array)) def tan(x: array, /) -> array: From b0b2539208a650ef5651fdfb9c16d57c8412d1c7 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 26 Apr 2021 16:55:00 -0600 Subject: [PATCH 064/151] Add meshgrid(), broadcast_arrays(), broadcast_to(), and can_cast() to the array API namespace --- numpy/_array_api/__init__.py | 8 +++--- numpy/_array_api/_creation_functions.py | 15 +++++++++-- numpy/_array_api/_data_type_functions.py | 34 +++++++++++++++++++++--- numpy/_array_api/_types.py | 4 +-- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index ebbe0bb9137..56699b09da0 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -116,13 +116,13 @@ __all__ += ['e', 'inf', 'nan', 'pi'] -from ._creation_functions import asarray, arange, empty, empty_like, eye, from_dlpack, full, full_like, linspace, ones, ones_like, zeros, zeros_like +from ._creation_functions import asarray, arange, empty, empty_like, eye, from_dlpack, full, full_like, linspace, meshgrid, ones, ones_like, zeros, zeros_like -__all__ += ['asarray', 'arange', 'empty', 'empty_like', 'eye', 'from_dlpack', 'full', 'full_like', 'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like'] +__all__ += ['asarray', 'arange', 'empty', 'empty_like', 'eye', 'from_dlpack', 'full', 'full_like', 'linspace', 'meshgrid', 'ones', 'ones_like', 'zeros', 'zeros_like'] -from ._data_type_functions import finfo, iinfo, result_type +from ._data_type_functions import broadcast_arrays, broadcast_to, can_cast, finfo, iinfo, result_type -__all__ += ['finfo', 'iinfo', 'result_type'] +__all__ += ['broadcast_arrays', 'broadcast_to', 'can_cast', 'finfo', 'iinfo', 'result_type'] from ._dtypes import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 003b10afb32..c6db3cb7b7d 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -3,8 +3,10 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import (Optional, SupportsDLPack, SupportsBufferProtocol, Tuple, - Union, array, device, dtype) + from ._types import (List, Optional, SupportsDLPack, + SupportsBufferProtocol, Tuple, Union, array, device, + dtype) + from collections.abc import Sequence from ._dtypes import _all_dtypes import numpy as np @@ -135,6 +137,15 @@ def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) +def meshgrid(*arrays: Sequence[array], indexing: str = 'xy') -> List[array, ...]: + """ + Array API compatible wrapper for :py:func:`np.meshgrid `. + + See its docstring for more information. + """ + from ._array_object import ndarray + return [ndarray._new(array) for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)] + def ones(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.ones `. diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index d4816a41f7d..81eacfe0f66 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -4,12 +4,40 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Union, array, dtype - -from collections.abc import Sequence + from ._types import List, Tuple, Union, array, dtype + from collections.abc import Sequence import numpy as np +def broadcast_arrays(*arrays: Sequence[array]) -> List[array]: + """ + Array API compatible wrapper for :py:func:`np.broadcast_arrays `. + + See its docstring for more information. + """ + from ._array_object import ndarray + return [ndarray._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays])] + +def broadcast_to(x: array, shape: Tuple[int, ...], /) -> array: + """ + Array API compatible wrapper for :py:func:`np.broadcast_to `. + + See its docstring for more information. + """ + from ._array_object import ndarray + return ndarray._new(np.broadcast_to(x._array, shape)) + +def can_cast(from_: Union[dtype, array], to: dtype, /) -> bool: + """ + Array API compatible wrapper for :py:func:`np.can_cast `. + + See its docstring for more information. + """ + from ._array_object import ndarray + if isinstance(from_, ndarray): + from_ = from_._array + return np.can_cast(from_, to) + def finfo(type: Union[dtype, array], /) -> finfo_object: """ Array API compatible wrapper for :py:func:`np.finfo `. diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index 32d03e2a771..36c9aa610ec 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -6,10 +6,10 @@ valid for inputs that match the given type annotations. """ -__all__ = ['Literal', 'Optional', 'Tuple', 'Union', 'array', 'device', +__all__ = ['List', 'Literal', 'Optional', 'Tuple', 'Union', 'array', 'device', 'dtype', 'SupportsDLPack', 'SupportsBufferProtocol', 'PyCapsule'] -from typing import Literal, Optional, Tuple, Union, TypeVar +from typing import List, Literal, Optional, Tuple, Union, TypeVar from . import (ndarray, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) From 6115cce356868d4b62ac25fc6777e3bdd0a7eb57 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 28 Apr 2021 16:36:11 -0600 Subject: [PATCH 065/151] Update signatures in the array API namespace from the latest version of the spec --- numpy/_array_api/_creation_functions.py | 18 +++++++++--------- numpy/_array_api/_data_type_functions.py | 2 +- numpy/_array_api/_manipulation_functions.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index c6db3cb7b7d..08dc772b54b 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -37,7 +37,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") return ndarray._new(res) -def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.arange `. @@ -49,7 +49,7 @@ def arange(start: Union[int, float], /, *, stop: Optional[Union[int, float]] = N raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.arange(start, stop=stop, step=step, dtype=dtype)) -def empty(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.empty `. @@ -73,7 +73,7 @@ def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.empty_like(x._array, dtype=dtype)) -def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.eye `. @@ -83,13 +83,13 @@ def eye(N: int, /, *, M: Optional[int] = None, k: Optional[int] = 0, dtype: Opti if device is not None: # Note: Device support is not yet implemented on ndarray raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.eye(N, M=M, k=k, dtype=dtype)) + return ndarray._new(np.eye(n_rows, M=n_cols, k=k, dtype=dtype)) def from_dlpack(x: object, /) -> array: # Note: dlpack support is not yet implemented on ndarray raise NotImplementedError("DLPack support is not yet implemented") -def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.full `. @@ -108,7 +108,7 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], /, * raise TypeError("Invalid input to full") return ndarray._new(res) -def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def full_like(x: array, /, fill_value: Union[int, float], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.full_like `. @@ -125,7 +125,7 @@ def full_like(x: array, fill_value: Union[int, float], /, *, dtype: Optional[dty raise TypeError("Invalid input to full_like") return ndarray._new(res) -def linspace(start: Union[int, float], stop: Union[int, float], num: int, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: +def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: """ Array API compatible wrapper for :py:func:`np.linspace `. @@ -146,7 +146,7 @@ def meshgrid(*arrays: Sequence[array], indexing: str = 'xy') -> List[array, ...] from ._array_object import ndarray return [ndarray._new(array) for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)] -def ones(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.ones `. @@ -170,7 +170,7 @@ def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[de raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.ones_like(x._array, dtype=dtype)) -def zeros(shape: Union[int, Tuple[int, ...]], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: """ Array API compatible wrapper for :py:func:`np.zeros `. diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 81eacfe0f66..03a857dfcfc 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -18,7 +18,7 @@ def broadcast_arrays(*arrays: Sequence[array]) -> List[array]: from ._array_object import ndarray return [ndarray._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays])] -def broadcast_to(x: array, shape: Tuple[int, ...], /) -> array: +def broadcast_to(x: array, /, shape: Tuple[int, ...]) -> array: """ Array API compatible wrapper for :py:func:`np.broadcast_to `. diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 6ac7be02f85..fb9e25baabd 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -18,7 +18,7 @@ def concat(arrays: Tuple[array, ...], /, *, axis: Optional[int] = 0) -> array: arrays = tuple(a._array for a in arrays) return ndarray._new(np.concatenate(arrays, axis=axis)) -def expand_dims(x: array, axis: int, /) -> array: +def expand_dims(x: array, /, *, axis: int) -> array: """ Array API compatible wrapper for :py:func:`np.expand_dims `. @@ -34,7 +34,7 @@ def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> """ return ndarray._new(np.flip(x._array, axis=axis)) -def reshape(x: array, shape: Tuple[int, ...], /) -> array: +def reshape(x: array, /, shape: Tuple[int, ...]) -> array: """ Array API compatible wrapper for :py:func:`np.reshape `. @@ -42,7 +42,7 @@ def reshape(x: array, shape: Tuple[int, ...], /) -> array: """ return ndarray._new(np.reshape(x._array, shape)) -def roll(x: array, shift: Union[int, Tuple[int, ...]], /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: +def roll(x: array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.roll `. From edf68c5bcc0df076af25f65c561350d98c05402f Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 30 Apr 2021 17:18:01 -0600 Subject: [PATCH 066/151] Fix some error messages --- numpy/_array_api/_array_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index d1aa8d3fb59..1410020e227 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -215,7 +215,7 @@ def __float__(self: array, /) -> float: """ # Note: This is an error here. if self._array.shape != (): - raise TypeError("bool is only allowed on arrays with shape ()") + raise TypeError("float is only allowed on arrays with shape ()") res = self._array.__float__() return res @@ -372,7 +372,7 @@ def __int__(self: array, /) -> int: """ # Note: This is an error here. if self._array.shape != (): - raise TypeError("bool is only allowed on arrays with shape ()") + raise TypeError("int is only allowed on arrays with shape ()") res = self._array.__int__() return res From 219968727b6a28e8564a22284cb630a808bc0c04 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 10 May 2021 15:07:36 -0600 Subject: [PATCH 067/151] Fix the array API norm() function --- numpy/_array_api/_linear_algebra_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index f57fe292aa8..99a386866d7 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -122,7 +122,7 @@ def norm(x: array, /, *, axis: Optional[Union[int, Tuple[int, int]]] = None, kee """ # Note: this is different from the default behavior if axis == None and x.ndim > 2: - x = x.flatten() + x = ndarray._new(x._array.flatten()) # Note: this function is being imported from a nondefault namespace return ndarray._new(np.linalg.norm(x._array, axis=axis, keepdims=keepdims, ord=ord)) From 533d0468f12e89b1b4b299f0344a31378853b012 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 10 May 2021 15:18:42 -0600 Subject: [PATCH 068/151] Fix array API squeeze() and stack() --- numpy/_array_api/_manipulation_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index fb9e25baabd..b461f6b6b02 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -56,7 +56,7 @@ def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) See its docstring for more information. """ - return ndarray._array(np.squeeze(x._array, axis=axis)) + return ndarray._new(np.squeeze(x._array, axis=axis)) def stack(arrays: Tuple[array, ...], /, *, axis: int = 0) -> array: """ @@ -65,4 +65,4 @@ def stack(arrays: Tuple[array, ...], /, *, axis: int = 0) -> array: See its docstring for more information. """ arrays = tuple(a._array for a in arrays) - return ndarray._array(np.stack(arrays, axis=axis)) + return ndarray._new(np.stack(arrays, axis=axis)) From 4817784c6e1050034faabb1b3d04382fe8997b41 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 10 May 2021 15:35:40 -0600 Subject: [PATCH 069/151] Make the array API constants Python floats --- numpy/_array_api/_constants.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/numpy/_array_api/_constants.py b/numpy/_array_api/_constants.py index 5fde3462586..9541941e7c6 100644 --- a/numpy/_array_api/_constants.py +++ b/numpy/_array_api/_constants.py @@ -1,9 +1,6 @@ -from ._array_object import ndarray -from ._dtypes import float64 - import numpy as np -e = ndarray._new(np.array(np.e, dtype=float64)) -inf = ndarray._new(np.array(np.inf, dtype=float64)) -nan = ndarray._new(np.array(np.nan, dtype=float64)) -pi = ndarray._new(np.array(np.pi, dtype=float64)) +e = np.e +inf = np.inf +nan = np.nan +pi = np.pi From 96f40fed3f08043986adb3db860cf0e647b27085 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 17 May 2021 16:43:31 -0600 Subject: [PATCH 070/151] Ignore warnings in array API functions that can raise them --- numpy/_array_api/_array_object.py | 25 +++++++++++++++++++++- numpy/_array_api/_elementwise_functions.py | 25 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 1410020e227..119992bdc13 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -149,6 +149,7 @@ def __abs__(self: array, /) -> array: res = self._array.__abs__() return self.__class__._new(res) + @np.errstate(all='ignore') def __add__(self: array, other: array, /) -> array: """ Performs the operation __add__. @@ -196,6 +197,7 @@ def __dlpack_device__(self: array, /) -> Tuple[IntEnum, int]: """ Performs the operation __dlpack_device__. """ + # Note: device support is required for this res = self._array.__dlpack_device__() return self.__class__._new(res) @@ -219,6 +221,7 @@ def __float__(self: array, /) -> float: res = self._array.__float__() return res + @np.errstate(all='ignore') def __floordiv__(self: array, other: array, /) -> array: """ Performs the operation __floordiv__. @@ -434,6 +437,7 @@ def __matmul__(self: array, other: array, /) -> array: res = self._array.__matmul__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __mod__(self: array, other: array, /) -> array: """ Performs the operation __mod__. @@ -444,6 +448,7 @@ def __mod__(self: array, other: array, /) -> array: res = self._array.__mod__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __mul__(self: array, other: array, /) -> array: """ Performs the operation __mul__. @@ -488,6 +493,7 @@ def __pos__(self: array, /) -> array: res = self._array.__pos__() return self.__class__._new(res) + @np.errstate(all='ignore') def __pow__(self: array, other: array, /) -> array: """ Performs the operation __pow__. @@ -523,6 +529,7 @@ def __setitem__(self, key, value, /): res = self._array.__setitem__(key, asarray(value)._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __sub__(self: array, other: array, /) -> array: """ Performs the operation __sub__. @@ -533,6 +540,7 @@ def __sub__(self: array, other: array, /) -> array: res = self._array.__sub__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __truediv__(self: array, other: array, /) -> array: """ Performs the operation __truediv__. @@ -553,6 +561,7 @@ def __xor__(self: array, other: array, /) -> array: res = self._array.__xor__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __iadd__(self: array, other: array, /) -> array: """ Performs the operation __iadd__. @@ -564,6 +573,7 @@ def __iadd__(self: array, other: array, /) -> array: raise RuntimeError return self.__class__._new(res) + @np.errstate(all='ignore') def __radd__(self: array, other: array, /) -> array: """ Performs the operation __radd__. @@ -593,6 +603,7 @@ def __rand__(self: array, other: array, /) -> array: res = self._array.__rand__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __ifloordiv__(self: array, other: array, /) -> array: """ Performs the operation __ifloordiv__. @@ -602,6 +613,7 @@ def __ifloordiv__(self: array, other: array, /) -> array: res = self._array.__ifloordiv__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __rfloordiv__(self: array, other: array, /) -> array: """ Performs the operation __rfloordiv__. @@ -656,6 +668,7 @@ def __rmatmul__(self: array, other: array, /) -> array: res = self._array.__rmatmul__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __imod__(self: array, other: array, /) -> array: """ Performs the operation __imod__. @@ -665,6 +678,7 @@ def __imod__(self: array, other: array, /) -> array: res = self._array.__imod__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __rmod__(self: array, other: array, /) -> array: """ Performs the operation __rmod__. @@ -675,6 +689,7 @@ def __rmod__(self: array, other: array, /) -> array: res = self._array.__rmod__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __imul__(self: array, other: array, /) -> array: """ Performs the operation __imul__. @@ -684,6 +699,7 @@ def __imul__(self: array, other: array, /) -> array: res = self._array.__imul__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __rmul__(self: array, other: array, /) -> array: """ Performs the operation __rmul__. @@ -713,6 +729,7 @@ def __ror__(self: array, other: array, /) -> array: res = self._array.__ror__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __ipow__(self: array, other: array, /) -> array: """ Performs the operation __ipow__. @@ -722,6 +739,7 @@ def __ipow__(self: array, other: array, /) -> array: res = self._array.__ipow__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __rpow__(self: array, other: array, /) -> array: """ Performs the operation __rpow__. @@ -756,6 +774,7 @@ def __rrshift__(self: array, other: array, /) -> array: res = self._array.__rrshift__(other._array).astype(other.dtype) return self.__class__._new(res) + @np.errstate(all='ignore') def __isub__(self: array, other: array, /) -> array: """ Performs the operation __isub__. @@ -765,6 +784,7 @@ def __isub__(self: array, other: array, /) -> array: res = self._array.__isub__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __rsub__(self: array, other: array, /) -> array: """ Performs the operation __rsub__. @@ -775,6 +795,7 @@ def __rsub__(self: array, other: array, /) -> array: res = self._array.__rsub__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __itruediv__(self: array, other: array, /) -> array: """ Performs the operation __itruediv__. @@ -784,6 +805,7 @@ def __itruediv__(self: array, other: array, /) -> array: res = self._array.__itruediv__(other._array) return self.__class__._new(res) + @np.errstate(all='ignore') def __rtruediv__(self: array, other: array, /) -> array: """ Performs the operation __rtruediv__. @@ -829,7 +851,8 @@ def device(self): See its docstring for more information. """ - return self._array.device + # Note: device support is required for this + raise NotImplementedError("The device attribute is not yet implemented") @property def ndim(self): diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index cb855da1261..197e773242a 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -22,6 +22,7 @@ def abs(x: array, /) -> array: return ndarray._new(np.abs(x._array)) # Note: the function name is different here +@np.errstate(all='ignore') def acos(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arccos `. @@ -33,6 +34,7 @@ def acos(x: array, /) -> array: return ndarray._new(np.arccos(x._array)) # Note: the function name is different here +@np.errstate(all='ignore') def acosh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arccosh `. @@ -43,6 +45,7 @@ def acosh(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in acosh') return ndarray._new(np.arccosh(x._array)) +@np.errstate(all='ignore') def add(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.add `. @@ -55,6 +58,7 @@ def add(x1: array, x2: array, /) -> array: return ndarray._new(np.add(x1._array, x2._array)) # Note: the function name is different here +@np.errstate(all='ignore') def asin(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arcsin `. @@ -66,6 +70,7 @@ def asin(x: array, /) -> array: return ndarray._new(np.arcsin(x._array)) # Note: the function name is different here +@np.errstate(all='ignore') def asinh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arcsinh `. @@ -100,6 +105,7 @@ def atan2(x1: array, x2: array, /) -> array: return ndarray._new(np.arctan2(x1._array, x2._array)) # Note: the function name is different here +@np.errstate(all='ignore') def atanh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.arctanh `. @@ -203,6 +209,7 @@ def ceil(x: array, /) -> array: return x return ndarray._new(np.ceil(x._array)) +@np.errstate(all='ignore') def cos(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.cos `. @@ -213,6 +220,7 @@ def cos(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in cos') return ndarray._new(np.cos(x._array)) +@np.errstate(all='ignore') def cosh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.cosh `. @@ -223,6 +231,7 @@ def cosh(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in cosh') return ndarray._new(np.cosh(x._array)) +@np.errstate(all='ignore') def divide(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.divide `. @@ -243,6 +252,7 @@ def equal(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.equal(x1._array, x2._array)) +@np.errstate(all='ignore') def exp(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.exp `. @@ -253,6 +263,7 @@ def exp(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in exp') return ndarray._new(np.exp(x._array)) +@np.errstate(all='ignore') def expm1(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.expm1 `. @@ -276,6 +287,7 @@ def floor(x: array, /) -> array: return x return ndarray._new(np.floor(x._array)) +@np.errstate(all='ignore') def floor_divide(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.floor_divide `. @@ -361,6 +373,7 @@ def less_equal(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.less_equal(x1._array, x2._array)) +@np.errstate(all='ignore') def log(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log `. @@ -371,6 +384,7 @@ def log(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in log') return ndarray._new(np.log(x._array)) +@np.errstate(all='ignore') def log1p(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log1p `. @@ -381,6 +395,7 @@ def log1p(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in log1p') return ndarray._new(np.log1p(x._array)) +@np.errstate(all='ignore') def log2(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log2 `. @@ -391,6 +406,7 @@ def log2(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in log2') return ndarray._new(np.log2(x._array)) +@np.errstate(all='ignore') def log10(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.log10 `. @@ -455,6 +471,7 @@ def logical_xor(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logical_xor(x1._array, x2._array)) +@np.errstate(all='ignore') def multiply(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.multiply `. @@ -496,6 +513,7 @@ def positive(x: array, /) -> array: return ndarray._new(np.positive(x._array)) # Note: the function name is different here +@np.errstate(all='ignore') def pow(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.power `. @@ -507,6 +525,7 @@ def pow(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.power(x1._array, x2._array)) +@np.errstate(all='ignore') def remainder(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.remainder `. @@ -538,6 +557,7 @@ def sign(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in sign') return ndarray._new(np.sign(x._array)) +@np.errstate(all='ignore') def sin(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sin `. @@ -548,6 +568,7 @@ def sin(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in sin') return ndarray._new(np.sin(x._array)) +@np.errstate(all='ignore') def sinh(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sinh `. @@ -558,6 +579,7 @@ def sinh(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in sinh') return ndarray._new(np.sinh(x._array)) +@np.errstate(all='ignore') def square(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.square `. @@ -568,6 +590,7 @@ def square(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in square') return ndarray._new(np.square(x._array)) +@np.errstate(all='ignore') def sqrt(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.sqrt `. @@ -578,6 +601,7 @@ def sqrt(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in sqrt') return ndarray._new(np.sqrt(x._array)) +@np.errstate(all='ignore') def subtract(x1: array, x2: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.subtract `. @@ -589,6 +613,7 @@ def subtract(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.subtract(x1._array, x2._array)) +@np.errstate(all='ignore') def tan(x: array, /) -> array: """ Array API compatible wrapper for :py:func:`np.tan `. From be1ee6c93e63da3a7766a504304755283fb1411a Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 20 May 2021 16:38:44 -0600 Subject: [PATCH 071/151] Update signatures from the latest version of the array API spec --- numpy/_array_api/_array_object.py | 84 ++++++++++----------- numpy/_array_api/_manipulation_functions.py | 2 +- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 119992bdc13..30858b7c548 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -150,7 +150,7 @@ def __abs__(self: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __add__(self: array, other: array, /) -> array: + def __add__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __add__. """ @@ -160,7 +160,7 @@ def __add__(self: array, other: array, /) -> array: res = self._array.__add__(other._array) return self.__class__._new(res) - def __and__(self: array, other: array, /) -> array: + def __and__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __and__. """ @@ -201,7 +201,7 @@ def __dlpack_device__(self: array, /) -> Tuple[IntEnum, int]: res = self._array.__dlpack_device__() return self.__class__._new(res) - def __eq__(self: array, other: array, /) -> array: + def __eq__(self: array, other: Union[int, float, bool, array], /) -> array: """ Performs the operation __eq__. """ @@ -222,7 +222,7 @@ def __float__(self: array, /) -> float: return res @np.errstate(all='ignore') - def __floordiv__(self: array, other: array, /) -> array: + def __floordiv__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __floordiv__. """ @@ -232,7 +232,7 @@ def __floordiv__(self: array, other: array, /) -> array: res = self._array.__floordiv__(other._array) return self.__class__._new(res) - def __ge__(self: array, other: array, /) -> array: + def __ge__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __ge__. """ @@ -359,7 +359,7 @@ def __getitem__(self: array, key: Union[int, slice, ellipsis, Tuple[Union[int, s res = self._array.__getitem__(key) return self.__class__._new(res) - def __gt__(self: array, other: array, /) -> array: + def __gt__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __gt__. """ @@ -386,7 +386,7 @@ def __invert__(self: array, /) -> array: res = self._array.__invert__() return self.__class__._new(res) - def __le__(self: array, other: array, /) -> array: + def __le__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __le__. """ @@ -403,7 +403,7 @@ def __len__(self, /): res = self._array.__len__() return self.__class__._new(res) - def __lshift__(self: array, other: array, /) -> array: + def __lshift__(self: array, other: Union[int, array], /) -> array: """ Performs the operation __lshift__. """ @@ -416,7 +416,7 @@ def __lshift__(self: array, other: array, /) -> array: res = self._array.__lshift__(other._array).astype(self.dtype) return self.__class__._new(res) - def __lt__(self: array, other: array, /) -> array: + def __lt__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __lt__. """ @@ -438,7 +438,7 @@ def __matmul__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __mod__(self: array, other: array, /) -> array: + def __mod__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __mod__. """ @@ -449,7 +449,7 @@ def __mod__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __mul__(self: array, other: array, /) -> array: + def __mul__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __mul__. """ @@ -459,7 +459,7 @@ def __mul__(self: array, other: array, /) -> array: res = self._array.__mul__(other._array) return self.__class__._new(res) - def __ne__(self: array, other: array, /) -> array: + def __ne__(self: array, other: Union[int, float, bool, array], /) -> array: """ Performs the operation __ne__. """ @@ -476,7 +476,7 @@ def __neg__(self: array, /) -> array: res = self._array.__neg__() return self.__class__._new(res) - def __or__(self: array, other: array, /) -> array: + def __or__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __or__. """ @@ -494,7 +494,7 @@ def __pos__(self: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __pow__(self: array, other: array, /) -> array: + def __pow__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __pow__. """ @@ -506,7 +506,7 @@ def __pow__(self: array, other: array, /) -> array: # arrays, so we use pow() here instead. return pow(self, other) - def __rshift__(self: array, other: array, /) -> array: + def __rshift__(self: array, other: Union[int, array], /) -> array: """ Performs the operation __rshift__. """ @@ -530,7 +530,7 @@ def __setitem__(self, key, value, /): return self.__class__._new(res) @np.errstate(all='ignore') - def __sub__(self: array, other: array, /) -> array: + def __sub__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __sub__. """ @@ -541,7 +541,7 @@ def __sub__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __truediv__(self: array, other: array, /) -> array: + def __truediv__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __truediv__. """ @@ -551,7 +551,7 @@ def __truediv__(self: array, other: array, /) -> array: res = self._array.__truediv__(other._array) return self.__class__._new(res) - def __xor__(self: array, other: array, /) -> array: + def __xor__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __xor__. """ @@ -562,7 +562,7 @@ def __xor__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __iadd__(self: array, other: array, /) -> array: + def __iadd__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __iadd__. """ @@ -574,7 +574,7 @@ def __iadd__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __radd__(self: array, other: array, /) -> array: + def __radd__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __radd__. """ @@ -584,7 +584,7 @@ def __radd__(self: array, other: array, /) -> array: res = self._array.__radd__(other._array) return self.__class__._new(res) - def __iand__(self: array, other: array, /) -> array: + def __iand__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __iand__. """ @@ -593,7 +593,7 @@ def __iand__(self: array, other: array, /) -> array: res = self._array.__iand__(other._array) return self.__class__._new(res) - def __rand__(self: array, other: array, /) -> array: + def __rand__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __rand__. """ @@ -604,7 +604,7 @@ def __rand__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __ifloordiv__(self: array, other: array, /) -> array: + def __ifloordiv__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __ifloordiv__. """ @@ -614,7 +614,7 @@ def __ifloordiv__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __rfloordiv__(self: array, other: array, /) -> array: + def __rfloordiv__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __rfloordiv__. """ @@ -624,7 +624,7 @@ def __rfloordiv__(self: array, other: array, /) -> array: res = self._array.__rfloordiv__(other._array) return self.__class__._new(res) - def __ilshift__(self: array, other: array, /) -> array: + def __ilshift__(self: array, other: Union[int, array], /) -> array: """ Performs the operation __ilshift__. """ @@ -633,7 +633,7 @@ def __ilshift__(self: array, other: array, /) -> array: res = self._array.__ilshift__(other._array) return self.__class__._new(res) - def __rlshift__(self: array, other: array, /) -> array: + def __rlshift__(self: array, other: Union[int, array], /) -> array: """ Performs the operation __rlshift__. """ @@ -669,7 +669,7 @@ def __rmatmul__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __imod__(self: array, other: array, /) -> array: + def __imod__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __imod__. """ @@ -679,7 +679,7 @@ def __imod__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __rmod__(self: array, other: array, /) -> array: + def __rmod__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __rmod__. """ @@ -690,7 +690,7 @@ def __rmod__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __imul__(self: array, other: array, /) -> array: + def __imul__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __imul__. """ @@ -700,7 +700,7 @@ def __imul__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __rmul__(self: array, other: array, /) -> array: + def __rmul__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __rmul__. """ @@ -710,7 +710,7 @@ def __rmul__(self: array, other: array, /) -> array: res = self._array.__rmul__(other._array) return self.__class__._new(res) - def __ior__(self: array, other: array, /) -> array: + def __ior__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __ior__. """ @@ -719,7 +719,7 @@ def __ior__(self: array, other: array, /) -> array: res = self._array.__ior__(other._array) return self.__class__._new(res) - def __ror__(self: array, other: array, /) -> array: + def __ror__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __ror__. """ @@ -730,7 +730,7 @@ def __ror__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __ipow__(self: array, other: array, /) -> array: + def __ipow__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __ipow__. """ @@ -740,7 +740,7 @@ def __ipow__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __rpow__(self: array, other: array, /) -> array: + def __rpow__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __rpow__. """ @@ -752,7 +752,7 @@ def __rpow__(self: array, other: array, /) -> array: # for 0-d arrays, so we use pow() here instead. return pow(other, self) - def __irshift__(self: array, other: array, /) -> array: + def __irshift__(self: array, other: Union[int, array], /) -> array: """ Performs the operation __irshift__. """ @@ -761,7 +761,7 @@ def __irshift__(self: array, other: array, /) -> array: res = self._array.__irshift__(other._array) return self.__class__._new(res) - def __rrshift__(self: array, other: array, /) -> array: + def __rrshift__(self: array, other: Union[int, array], /) -> array: """ Performs the operation __rrshift__. """ @@ -775,7 +775,7 @@ def __rrshift__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __isub__(self: array, other: array, /) -> array: + def __isub__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __isub__. """ @@ -785,7 +785,7 @@ def __isub__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __rsub__(self: array, other: array, /) -> array: + def __rsub__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __rsub__. """ @@ -796,7 +796,7 @@ def __rsub__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __itruediv__(self: array, other: array, /) -> array: + def __itruediv__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __itruediv__. """ @@ -806,7 +806,7 @@ def __itruediv__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __rtruediv__(self: array, other: array, /) -> array: + def __rtruediv__(self: array, other: Union[int, float, array], /) -> array: """ Performs the operation __rtruediv__. """ @@ -816,7 +816,7 @@ def __rtruediv__(self: array, other: array, /) -> array: res = self._array.__rtruediv__(other._array) return self.__class__._new(res) - def __ixor__(self: array, other: array, /) -> array: + def __ixor__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __ixor__. """ @@ -825,7 +825,7 @@ def __ixor__(self: array, other: array, /) -> array: res = self._array.__ixor__(other._array) return self.__class__._new(res) - def __rxor__(self: array, other: array, /) -> array: + def __rxor__(self: array, other: Union[int, bool, array], /) -> array: """ Performs the operation __rxor__. """ diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index b461f6b6b02..5f7b0a4515b 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -50,7 +50,7 @@ def roll(x: array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Unio """ return ndarray._new(np.roll(x._array, shift, axis=axis)) -def squeeze(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: +def squeeze(x: array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: """ Array API compatible wrapper for :py:func:`np.squeeze `. From f6015d2754dde04342ca2a0d719ca7f01d6e0dcb Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 14 Jun 2021 15:30:28 -0600 Subject: [PATCH 072/151] Update a function signature from the array API spec --- numpy/_array_api/_array_object.py | 4 ++-- numpy/_array_api/_types.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 30858b7c548..5f169ab6631 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, PyCapsule, Tuple, Union, array + from ._types import Any, Optional, PyCapsule, Tuple, Union, array import numpy as np @@ -186,7 +186,7 @@ def __bool__(self: array, /) -> bool: res = self._array.__bool__() return res - def __dlpack__(self: array, /, *, stream: Optional[int] = None) -> PyCapsule: + def __dlpack__(self: array, /, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule: """ Performs the operation __dlpack__. """ diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index 36c9aa610ec..1086699fcf2 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -6,10 +6,11 @@ valid for inputs that match the given type annotations. """ -__all__ = ['List', 'Literal', 'Optional', 'Tuple', 'Union', 'array', 'device', - 'dtype', 'SupportsDLPack', 'SupportsBufferProtocol', 'PyCapsule'] +__all__ = ['Any', 'List', 'Literal', 'Optional', 'Tuple', 'Union', 'array', + 'device', 'dtype', 'SupportsDLPack', 'SupportsBufferProtocol', + 'PyCapsule'] -from typing import List, Literal, Optional, Tuple, Union, TypeVar +from typing import Any, List, Literal, Optional, Tuple, Union, TypeVar from . import (ndarray, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) From cad21e94b58b125a4264f154e91a1730dcf550da Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 1 Jul 2021 15:48:19 -0600 Subject: [PATCH 073/151] Update the linear algebra functions in the array API namespace For now, only the functions in from the main spec namespace are implemented. The remaining linear algebra functions are part of an extension in the spec, and will be implemented in a future pull request. This is because the linear algebra functions are relatively complicated, so they will be easier to review separately. This also updates those functions that do remain for now to be more compliant with the spec. --- numpy/_array_api/__init__.py | 15 +- numpy/_array_api/_linear_algebra_functions.py | 181 +++--------------- 2 files changed, 31 insertions(+), 165 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 56699b09da0..e39a2c7d0f8 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -47,8 +47,8 @@ - np.argmin and np.argmax do not implement the keepdims keyword argument. - - Some linear algebra functions in the spec are still a work in progress (to - be added soon). These will be updated once the spec is. + - The linear algebra extension in the spec will be added in a future pull +request. - Some tests in the test suite are still not fully correct in that they test all datatypes whereas certain functions are only defined for a subset of @@ -132,13 +132,14 @@ __all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logaddexp', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] -from ._linear_algebra_functions import cross, det, diagonal, inv, norm, outer, trace, transpose +# einsum is not yet implemented in the array API spec. -__all__ += ['cross', 'det', 'diagonal', 'inv', 'norm', 'outer', 'trace', 'transpose'] +# from ._linear_algebra_functions import einsum +# __all__ += ['einsum'] -# from ._linear_algebra_functions import cholesky, cross, det, diagonal, dot, eig, eigvalsh, einsum, inv, lstsq, matmul, matrix_power, matrix_rank, norm, outer, pinv, qr, slogdet, solve, svd, trace, transpose -# -# __all__ += ['cholesky', 'cross', 'det', 'diagonal', 'dot', 'eig', 'eigvalsh', 'einsum', 'inv', 'lstsq', 'matmul', 'matrix_power', 'matrix_rank', 'norm', 'outer', 'pinv', 'qr', 'slogdet', 'solve', 'svd', 'trace', 'transpose'] +from ._linear_algebra_functions import matmul, tensordot, transpose, vecdot + +__all__ += ['matmul', 'tensordot', 'transpose', 'vecdot'] from ._manipulation_functions import concat, expand_dims, flip, reshape, roll, squeeze, stack diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index 99a386866d7..4617706415c 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,70 +1,16 @@ from __future__ import annotations from ._array_object import ndarray +from ._dtypes import _numeric_dtypes from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Literal, Optional, Tuple, Union, array + from ._types import Optional, Sequence, Tuple, Union, array import numpy as np -# def cholesky(): -# """ -# Array API compatible wrapper for :py:func:`np.cholesky `. -# -# See its docstring for more information. -# """ -# return np.cholesky() - -def cross(x1: array, x2: array, /, *, axis: int = -1) -> array: - """ - Array API compatible wrapper for :py:func:`np.cross `. +# einsum is not yet implemented in the array API spec. - See its docstring for more information. - """ - return ndarray._new(np.cross(x1._array, x2._array, axis=axis)) - -def det(x: array, /) -> array: - """ - Array API compatible wrapper for :py:func:`np.linalg.det `. - - See its docstring for more information. - """ - # Note: this function is being imported from a nondefault namespace - return ndarray._new(np.linalg.det(x._array)) - -def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> array: - """ - Array API compatible wrapper for :py:func:`np.diagonal `. - - See its docstring for more information. - """ - return ndarray._new(np.diagonal(x._array, axis1=axis1, axis2=axis2, offset=offset)) - -# def dot(): -# """ -# Array API compatible wrapper for :py:func:`np.dot `. -# -# See its docstring for more information. -# """ -# return np.dot() -# -# def eig(): -# """ -# Array API compatible wrapper for :py:func:`np.eig `. -# -# See its docstring for more information. -# """ -# return np.eig() -# -# def eigvalsh(): -# """ -# Array API compatible wrapper for :py:func:`np.eigvalsh `. -# -# See its docstring for more information. -# """ -# return np.eigvalsh() -# # def einsum(): # """ # Array API compatible wrapper for :py:func:`np.einsum `. @@ -73,114 +19,27 @@ def diagonal(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> # """ # return np.einsum() -def inv(x: array, /) -> array: - """ - Array API compatible wrapper for :py:func:`np.linalg.inv `. - - See its docstring for more information. - """ - # Note: this function is being imported from a nondefault namespace - return ndarray._new(np.linalg.inv(x._array)) - -# def lstsq(): -# """ -# Array API compatible wrapper for :py:func:`np.lstsq `. -# -# See its docstring for more information. -# """ -# return np.lstsq() -# -# def matmul(): -# """ -# Array API compatible wrapper for :py:func:`np.matmul `. -# -# See its docstring for more information. -# """ -# return np.matmul() -# -# def matrix_power(): -# """ -# Array API compatible wrapper for :py:func:`np.matrix_power `. -# -# See its docstring for more information. -# """ -# return np.matrix_power() -# -# def matrix_rank(): -# """ -# Array API compatible wrapper for :py:func:`np.matrix_rank `. -# -# See its docstring for more information. -# """ -# return np.matrix_rank() - -def norm(x: array, /, *, axis: Optional[Union[int, Tuple[int, int]]] = None, keepdims: bool = False, ord: Optional[Union[int, float, Literal[np.inf, -np.inf, 'fro', 'nuc']]] = None) -> array: - """ - Array API compatible wrapper for :py:func:`np.linalg.norm `. - - See its docstring for more information. - """ - # Note: this is different from the default behavior - if axis == None and x.ndim > 2: - x = ndarray._new(x._array.flatten()) - # Note: this function is being imported from a nondefault namespace - return ndarray._new(np.linalg.norm(x._array, axis=axis, keepdims=keepdims, ord=ord)) - -def outer(x1: array, x2: array, /) -> array: +def matmul(x1: array, x2: array, /) -> array: """ - Array API compatible wrapper for :py:func:`np.outer `. + Array API compatible wrapper for :py:func:`np.matmul `. See its docstring for more information. """ - return ndarray._new(np.outer(x1._array, x2._array)) + # Note: the restriction to numeric dtypes only is different from + # np.matmul. + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in matmul') -# def pinv(): -# """ -# Array API compatible wrapper for :py:func:`np.pinv `. -# -# See its docstring for more information. -# """ -# return np.pinv() -# -# def qr(): -# """ -# Array API compatible wrapper for :py:func:`np.qr `. -# -# See its docstring for more information. -# """ -# return np.qr() -# -# def slogdet(): -# """ -# Array API compatible wrapper for :py:func:`np.slogdet `. -# -# See its docstring for more information. -# """ -# return np.slogdet() -# -# def solve(): -# """ -# Array API compatible wrapper for :py:func:`np.solve `. -# -# See its docstring for more information. -# """ -# return np.solve() -# -# def svd(): -# """ -# Array API compatible wrapper for :py:func:`np.svd `. -# -# See its docstring for more information. -# """ -# return np.svd() + return ndarray._new(np.matmul(x1._array, x2._array)) -def trace(x: array, /, *, axis1: int = 0, axis2: int = 1, offset: int = 0) -> array: - """ - Array API compatible wrapper for :py:func:`np.trace `. +# Note: axes must be a tuple, unlike np.tensordot where it can be an array or array-like. +def tensordot(x1: array, x2: array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> array: + # Note: the restriction to numeric dtypes only is different from + # np.tensordot. + if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in tensordot') - See its docstring for more information. - """ - return ndarray._new(np.asarray(np.trace(x._array, axis1=axis1, axis2=axis2, offset=offset))) + return ndarray._new(np.tensordot(x1._array, x2._array, axes=axes)) def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: """ @@ -189,3 +48,9 @@ def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: See its docstring for more information. """ return ndarray._new(np.transpose(x._array, axes=axes)) + +# Note: vecdot is not in NumPy +def vecdot(x1: array, x2: array, /, *, axis: Optional[int] = None) -> array: + if axis is None: + axis = -1 + return tensordot(x1, x2, axes=((axis,), (axis,))) From 01780805fabd160514a25d44972d527c3c99f8c8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 8 Jul 2021 16:09:50 -0600 Subject: [PATCH 074/151] Fix in-place operators to not recreate the wrapper class --- numpy/_array_api/_array_object.py | 50 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 5f169ab6631..a3de25478d4 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -568,10 +568,8 @@ def __iadd__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__iadd__(other._array) - if res.dtype != self.dtype: - raise RuntimeError - return self.__class__._new(res) + self._array.__iadd__(other._array) + return self @np.errstate(all='ignore') def __radd__(self: array, other: Union[int, float, array], /) -> array: @@ -590,8 +588,8 @@ def __iand__(self: array, other: Union[int, bool, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__iand__(other._array) - return self.__class__._new(res) + self._array.__iand__(other._array) + return self def __rand__(self: array, other: Union[int, bool, array], /) -> array: """ @@ -610,8 +608,8 @@ def __ifloordiv__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ifloordiv__(other._array) - return self.__class__._new(res) + self._array.__ifloordiv__(other._array) + return self @np.errstate(all='ignore') def __rfloordiv__(self: array, other: Union[int, float, array], /) -> array: @@ -630,8 +628,8 @@ def __ilshift__(self: array, other: Union[int, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ilshift__(other._array) - return self.__class__._new(res) + self._array.__ilshift__(other._array) + return self def __rlshift__(self: array, other: Union[int, array], /) -> array: """ @@ -675,8 +673,8 @@ def __imod__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__imod__(other._array) - return self.__class__._new(res) + self._array.__imod__(other._array) + return self @np.errstate(all='ignore') def __rmod__(self: array, other: Union[int, float, array], /) -> array: @@ -696,8 +694,8 @@ def __imul__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__imul__(other._array) - return self.__class__._new(res) + self._array.__imul__(other._array) + return self @np.errstate(all='ignore') def __rmul__(self: array, other: Union[int, float, array], /) -> array: @@ -716,8 +714,8 @@ def __ior__(self: array, other: Union[int, bool, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ior__(other._array) - return self.__class__._new(res) + self._array.__ior__(other._array) + return self def __ror__(self: array, other: Union[int, bool, array], /) -> array: """ @@ -736,8 +734,8 @@ def __ipow__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ipow__(other._array) - return self.__class__._new(res) + self._array.__ipow__(other._array) + return self @np.errstate(all='ignore') def __rpow__(self: array, other: Union[int, float, array], /) -> array: @@ -758,8 +756,8 @@ def __irshift__(self: array, other: Union[int, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__irshift__(other._array) - return self.__class__._new(res) + self._array.__irshift__(other._array) + return self def __rrshift__(self: array, other: Union[int, array], /) -> array: """ @@ -781,8 +779,8 @@ def __isub__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__isub__(other._array) - return self.__class__._new(res) + self._array.__isub__(other._array) + return self @np.errstate(all='ignore') def __rsub__(self: array, other: Union[int, float, array], /) -> array: @@ -802,8 +800,8 @@ def __itruediv__(self: array, other: Union[int, float, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__itruediv__(other._array) - return self.__class__._new(res) + self._array.__itruediv__(other._array) + return self @np.errstate(all='ignore') def __rtruediv__(self: array, other: Union[int, float, array], /) -> array: @@ -822,8 +820,8 @@ def __ixor__(self: array, other: Union[int, bool, array], /) -> array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - res = self._array.__ixor__(other._array) - return self.__class__._new(res) + self._array.__ixor__(other._array) + return self def __rxor__(self: array, other: Union[int, bool, array], /) -> array: """ From 13796236295b344ee83e79c8a33ad6205c0095db Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 8 Jul 2021 16:10:27 -0600 Subject: [PATCH 075/151] Fix the __imatmul__ method in the array API namespace --- numpy/_array_api/_array_object.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index a3de25478d4..8f7252160b4 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -648,12 +648,21 @@ def __imatmul__(self: array, other: array, /) -> array: """ Performs the operation __imatmul__. """ + # Note: NumPy does not implement __imatmul__. + if isinstance(other, (int, float, bool)): # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. other = self._promote_scalar(other) - res = self._array.__imatmul__(other._array) - return self.__class__._new(res) + # __imatmul__ can only be allowed when it would not change the shape + # of self. + other_shape = other.shape + if self.shape == () or other_shape == (): + raise ValueError("@= requires at least one dimension") + if len(other_shape) == 1 or other_shape[-1] != other_shape[-2]: + raise ValueError("@= cannot change the shape of the input array") + self._array[:] = self._array.__matmul__(other._array) + return self def __rmatmul__(self: array, other: array, /) -> array: """ From fc1ff6fc3045482a72c359689ee7bfa7e3299985 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 8 Jul 2021 16:56:27 -0600 Subject: [PATCH 076/151] Capitalize the names of the type hint types in the array API That way they aren't ambiguous with the attributes with the same names. --- numpy/_array_api/_array_object.py | 118 +++++++++--------- numpy/_array_api/_creation_functions.py | 32 ++--- numpy/_array_api/_data_type_functions.py | 14 +-- numpy/_array_api/_elementwise_functions.py | 114 ++++++++--------- numpy/_array_api/_linear_algebra_functions.py | 10 +- numpy/_array_api/_manipulation_functions.py | 16 +-- numpy/_array_api/_searching_functions.py | 10 +- numpy/_array_api/_set_functions.py | 4 +- numpy/_array_api/_sorting_functions.py | 6 +- numpy/_array_api/_statistical_functions.py | 16 +-- numpy/_array_api/_types.py | 10 +- numpy/_array_api/_utility_functions.py | 6 +- 12 files changed, 178 insertions(+), 178 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 8f7252160b4..89ec3ba1aac 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Any, Optional, PyCapsule, Tuple, Union, array + from ._types import Any, Optional, PyCapsule, Tuple, Union, Array import numpy as np @@ -71,13 +71,13 @@ def __new__(cls, *args, **kwargs): # These functions are not required by the spec, but are implemented for # the sake of usability. - def __str__(self: array, /) -> str: + def __str__(self: Array, /) -> str: """ Performs the operation __str__. """ return self._array.__str__().replace('array', 'ndarray') - def __repr__(self: array, /) -> str: + def __repr__(self: Array, /) -> str: """ Performs the operation __repr__. """ @@ -142,7 +142,7 @@ def _normalize_two_args(x1, x2): # Everything below this line is required by the spec. - def __abs__(self: array, /) -> array: + def __abs__(self: Array, /) -> Array: """ Performs the operation __abs__. """ @@ -150,7 +150,7 @@ def __abs__(self: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __add__(self: array, other: Union[int, float, array], /) -> array: + def __add__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __add__. """ @@ -160,7 +160,7 @@ def __add__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__add__(other._array) return self.__class__._new(res) - def __and__(self: array, other: Union[int, bool, array], /) -> array: + def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __and__. """ @@ -170,13 +170,13 @@ def __and__(self: array, other: Union[int, bool, array], /) -> array: res = self._array.__and__(other._array) return self.__class__._new(res) - def __array_namespace__(self: array, /, *, api_version: Optional[str] = None) -> object: + def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: if api_version is not None: raise ValueError("Unrecognized array API version") from numpy import _array_api return _array_api - def __bool__(self: array, /) -> bool: + def __bool__(self: Array, /) -> bool: """ Performs the operation __bool__. """ @@ -186,14 +186,14 @@ def __bool__(self: array, /) -> bool: res = self._array.__bool__() return res - def __dlpack__(self: array, /, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule: + def __dlpack__(self: Array, /, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule: """ Performs the operation __dlpack__. """ res = self._array.__dlpack__(stream=None) return self.__class__._new(res) - def __dlpack_device__(self: array, /) -> Tuple[IntEnum, int]: + def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]: """ Performs the operation __dlpack_device__. """ @@ -201,7 +201,7 @@ def __dlpack_device__(self: array, /) -> Tuple[IntEnum, int]: res = self._array.__dlpack_device__() return self.__class__._new(res) - def __eq__(self: array, other: Union[int, float, bool, array], /) -> array: + def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: """ Performs the operation __eq__. """ @@ -211,7 +211,7 @@ def __eq__(self: array, other: Union[int, float, bool, array], /) -> array: res = self._array.__eq__(other._array) return self.__class__._new(res) - def __float__(self: array, /) -> float: + def __float__(self: Array, /) -> float: """ Performs the operation __float__. """ @@ -222,7 +222,7 @@ def __float__(self: array, /) -> float: return res @np.errstate(all='ignore') - def __floordiv__(self: array, other: Union[int, float, array], /) -> array: + def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __floordiv__. """ @@ -232,7 +232,7 @@ def __floordiv__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__floordiv__(other._array) return self.__class__._new(res) - def __ge__(self: array, other: Union[int, float, array], /) -> array: + def __ge__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ge__. """ @@ -349,7 +349,7 @@ def _validate_index(key, shape): # ndarray() form, like a list of booleans. raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") - def __getitem__(self: array, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array], /) -> array: + def __getitem__(self: Array, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], /) -> Array: """ Performs the operation __getitem__. """ @@ -359,7 +359,7 @@ def __getitem__(self: array, key: Union[int, slice, ellipsis, Tuple[Union[int, s res = self._array.__getitem__(key) return self.__class__._new(res) - def __gt__(self: array, other: Union[int, float, array], /) -> array: + def __gt__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __gt__. """ @@ -369,7 +369,7 @@ def __gt__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__gt__(other._array) return self.__class__._new(res) - def __int__(self: array, /) -> int: + def __int__(self: Array, /) -> int: """ Performs the operation __int__. """ @@ -379,14 +379,14 @@ def __int__(self: array, /) -> int: res = self._array.__int__() return res - def __invert__(self: array, /) -> array: + def __invert__(self: Array, /) -> Array: """ Performs the operation __invert__. """ res = self._array.__invert__() return self.__class__._new(res) - def __le__(self: array, other: Union[int, float, array], /) -> array: + def __le__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __le__. """ @@ -403,7 +403,7 @@ def __len__(self, /): res = self._array.__len__() return self.__class__._new(res) - def __lshift__(self: array, other: Union[int, array], /) -> array: + def __lshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __lshift__. """ @@ -416,7 +416,7 @@ def __lshift__(self: array, other: Union[int, array], /) -> array: res = self._array.__lshift__(other._array).astype(self.dtype) return self.__class__._new(res) - def __lt__(self: array, other: Union[int, float, array], /) -> array: + def __lt__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __lt__. """ @@ -426,7 +426,7 @@ def __lt__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__lt__(other._array) return self.__class__._new(res) - def __matmul__(self: array, other: array, /) -> array: + def __matmul__(self: Array, other: Array, /) -> Array: """ Performs the operation __matmul__. """ @@ -438,7 +438,7 @@ def __matmul__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __mod__(self: array, other: Union[int, float, array], /) -> array: + def __mod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mod__. """ @@ -449,7 +449,7 @@ def __mod__(self: array, other: Union[int, float, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __mul__(self: array, other: Union[int, float, array], /) -> array: + def __mul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mul__. """ @@ -459,7 +459,7 @@ def __mul__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__mul__(other._array) return self.__class__._new(res) - def __ne__(self: array, other: Union[int, float, bool, array], /) -> array: + def __ne__(self: Array, other: Union[int, float, bool, Array], /) -> Array: """ Performs the operation __ne__. """ @@ -469,14 +469,14 @@ def __ne__(self: array, other: Union[int, float, bool, array], /) -> array: res = self._array.__ne__(other._array) return self.__class__._new(res) - def __neg__(self: array, /) -> array: + def __neg__(self: Array, /) -> Array: """ Performs the operation __neg__. """ res = self._array.__neg__() return self.__class__._new(res) - def __or__(self: array, other: Union[int, bool, array], /) -> array: + def __or__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __or__. """ @@ -486,7 +486,7 @@ def __or__(self: array, other: Union[int, bool, array], /) -> array: res = self._array.__or__(other._array) return self.__class__._new(res) - def __pos__(self: array, /) -> array: + def __pos__(self: Array, /) -> Array: """ Performs the operation __pos__. """ @@ -494,7 +494,7 @@ def __pos__(self: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __pow__(self: array, other: Union[int, float, array], /) -> array: + def __pow__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __pow__. """ @@ -506,7 +506,7 @@ def __pow__(self: array, other: Union[int, float, array], /) -> array: # arrays, so we use pow() here instead. return pow(self, other) - def __rshift__(self: array, other: Union[int, array], /) -> array: + def __rshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rshift__. """ @@ -530,7 +530,7 @@ def __setitem__(self, key, value, /): return self.__class__._new(res) @np.errstate(all='ignore') - def __sub__(self: array, other: Union[int, float, array], /) -> array: + def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __sub__. """ @@ -541,7 +541,7 @@ def __sub__(self: array, other: Union[int, float, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __truediv__(self: array, other: Union[int, float, array], /) -> array: + def __truediv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __truediv__. """ @@ -551,7 +551,7 @@ def __truediv__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__truediv__(other._array) return self.__class__._new(res) - def __xor__(self: array, other: Union[int, bool, array], /) -> array: + def __xor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __xor__. """ @@ -562,7 +562,7 @@ def __xor__(self: array, other: Union[int, bool, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __iadd__(self: array, other: Union[int, float, array], /) -> array: + def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __iadd__. """ @@ -572,7 +572,7 @@ def __iadd__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __radd__(self: array, other: Union[int, float, array], /) -> array: + def __radd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __radd__. """ @@ -582,7 +582,7 @@ def __radd__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__radd__(other._array) return self.__class__._new(res) - def __iand__(self: array, other: Union[int, bool, array], /) -> array: + def __iand__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __iand__. """ @@ -591,7 +591,7 @@ def __iand__(self: array, other: Union[int, bool, array], /) -> array: self._array.__iand__(other._array) return self - def __rand__(self: array, other: Union[int, bool, array], /) -> array: + def __rand__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __rand__. """ @@ -602,7 +602,7 @@ def __rand__(self: array, other: Union[int, bool, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __ifloordiv__(self: array, other: Union[int, float, array], /) -> array: + def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ifloordiv__. """ @@ -612,7 +612,7 @@ def __ifloordiv__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __rfloordiv__(self: array, other: Union[int, float, array], /) -> array: + def __rfloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rfloordiv__. """ @@ -622,7 +622,7 @@ def __rfloordiv__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__rfloordiv__(other._array) return self.__class__._new(res) - def __ilshift__(self: array, other: Union[int, array], /) -> array: + def __ilshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __ilshift__. """ @@ -631,7 +631,7 @@ def __ilshift__(self: array, other: Union[int, array], /) -> array: self._array.__ilshift__(other._array) return self - def __rlshift__(self: array, other: Union[int, array], /) -> array: + def __rlshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rlshift__. """ @@ -644,7 +644,7 @@ def __rlshift__(self: array, other: Union[int, array], /) -> array: res = self._array.__rlshift__(other._array).astype(other.dtype) return self.__class__._new(res) - def __imatmul__(self: array, other: array, /) -> array: + def __imatmul__(self: Array, other: Array, /) -> Array: """ Performs the operation __imatmul__. """ @@ -664,7 +664,7 @@ def __imatmul__(self: array, other: array, /) -> array: self._array[:] = self._array.__matmul__(other._array) return self - def __rmatmul__(self: array, other: array, /) -> array: + def __rmatmul__(self: Array, other: Array, /) -> Array: """ Performs the operation __rmatmul__. """ @@ -676,7 +676,7 @@ def __rmatmul__(self: array, other: array, /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __imod__(self: array, other: Union[int, float, array], /) -> array: + def __imod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imod__. """ @@ -686,7 +686,7 @@ def __imod__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __rmod__(self: array, other: Union[int, float, array], /) -> array: + def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmod__. """ @@ -697,7 +697,7 @@ def __rmod__(self: array, other: Union[int, float, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __imul__(self: array, other: Union[int, float, array], /) -> array: + def __imul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imul__. """ @@ -707,7 +707,7 @@ def __imul__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __rmul__(self: array, other: Union[int, float, array], /) -> array: + def __rmul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmul__. """ @@ -717,7 +717,7 @@ def __rmul__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__rmul__(other._array) return self.__class__._new(res) - def __ior__(self: array, other: Union[int, bool, array], /) -> array: + def __ior__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ior__. """ @@ -726,7 +726,7 @@ def __ior__(self: array, other: Union[int, bool, array], /) -> array: self._array.__ior__(other._array) return self - def __ror__(self: array, other: Union[int, bool, array], /) -> array: + def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ror__. """ @@ -737,7 +737,7 @@ def __ror__(self: array, other: Union[int, bool, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __ipow__(self: array, other: Union[int, float, array], /) -> array: + def __ipow__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ipow__. """ @@ -747,7 +747,7 @@ def __ipow__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __rpow__(self: array, other: Union[int, float, array], /) -> array: + def __rpow__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rpow__. """ @@ -759,7 +759,7 @@ def __rpow__(self: array, other: Union[int, float, array], /) -> array: # for 0-d arrays, so we use pow() here instead. return pow(other, self) - def __irshift__(self: array, other: Union[int, array], /) -> array: + def __irshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __irshift__. """ @@ -768,7 +768,7 @@ def __irshift__(self: array, other: Union[int, array], /) -> array: self._array.__irshift__(other._array) return self - def __rrshift__(self: array, other: Union[int, array], /) -> array: + def __rrshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rrshift__. """ @@ -782,7 +782,7 @@ def __rrshift__(self: array, other: Union[int, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __isub__(self: array, other: Union[int, float, array], /) -> array: + def __isub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __isub__. """ @@ -792,7 +792,7 @@ def __isub__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __rsub__(self: array, other: Union[int, float, array], /) -> array: + def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rsub__. """ @@ -803,7 +803,7 @@ def __rsub__(self: array, other: Union[int, float, array], /) -> array: return self.__class__._new(res) @np.errstate(all='ignore') - def __itruediv__(self: array, other: Union[int, float, array], /) -> array: + def __itruediv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __itruediv__. """ @@ -813,7 +813,7 @@ def __itruediv__(self: array, other: Union[int, float, array], /) -> array: return self @np.errstate(all='ignore') - def __rtruediv__(self: array, other: Union[int, float, array], /) -> array: + def __rtruediv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rtruediv__. """ @@ -823,7 +823,7 @@ def __rtruediv__(self: array, other: Union[int, float, array], /) -> array: res = self._array.__rtruediv__(other._array) return self.__class__._new(res) - def __ixor__(self: array, other: Union[int, bool, array], /) -> array: + def __ixor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ixor__. """ @@ -832,7 +832,7 @@ def __ixor__(self: array, other: Union[int, bool, array], /) -> array: self._array.__ixor__(other._array) return self - def __rxor__(self: array, other: Union[int, bool, array], /) -> array: + def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __rxor__. """ diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 08dc772b54b..9845dd70faf 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -4,14 +4,14 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from ._types import (List, Optional, SupportsDLPack, - SupportsBufferProtocol, Tuple, Union, array, device, - dtype) + SupportsBufferProtocol, Tuple, Union, Array, Device, + Dtype) from collections.abc import Sequence from ._dtypes import _all_dtypes import numpy as np -def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[dtype] = None, device: Optional[device] = None, copy: Optional[bool] = None) -> array: +def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.asarray `. @@ -37,7 +37,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") return ndarray._new(res) -def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.arange `. @@ -49,7 +49,7 @@ def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.arange(start, stop=stop, step=step, dtype=dtype)) -def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.empty `. @@ -61,7 +61,7 @@ def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.empty(shape, dtype=dtype)) -def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.empty_like `. @@ -73,7 +73,7 @@ def empty_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[d raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.empty_like(x._array, dtype=dtype)) -def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.eye `. @@ -85,11 +85,11 @@ def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, d raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.eye(n_rows, M=n_cols, k=k, dtype=dtype)) -def from_dlpack(x: object, /) -> array: +def from_dlpack(x: object, /) -> Array: # Note: dlpack support is not yet implemented on ndarray raise NotImplementedError("DLPack support is not yet implemented") -def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.full `. @@ -108,7 +108,7 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d raise TypeError("Invalid input to full") return ndarray._new(res) -def full_like(x: array, /, fill_value: Union[int, float], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.full_like `. @@ -125,7 +125,7 @@ def full_like(x: array, /, fill_value: Union[int, float], *, dtype: Optional[dty raise TypeError("Invalid input to full_like") return ndarray._new(res) -def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, dtype: Optional[dtype] = None, device: Optional[device] = None, endpoint: bool = True) -> array: +def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, endpoint: bool = True) -> Array: """ Array API compatible wrapper for :py:func:`np.linspace `. @@ -137,7 +137,7 @@ def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) -def meshgrid(*arrays: Sequence[array], indexing: str = 'xy') -> List[array, ...]: +def meshgrid(*arrays: Sequence[Array], indexing: str = 'xy') -> List[Array, ...]: """ Array API compatible wrapper for :py:func:`np.meshgrid `. @@ -146,7 +146,7 @@ def meshgrid(*arrays: Sequence[array], indexing: str = 'xy') -> List[array, ...] from ._array_object import ndarray return [ndarray._new(array) for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)] -def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.ones `. @@ -158,7 +158,7 @@ def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, d raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.ones(shape, dtype=dtype)) -def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.ones_like `. @@ -170,7 +170,7 @@ def ones_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[de raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.ones_like(x._array, dtype=dtype)) -def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.zeros `. @@ -182,7 +182,7 @@ def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[dtype] = None, raise NotImplementedError("Device support is not yet implemented") return ndarray._new(np.zeros(shape, dtype=dtype)) -def zeros_like(x: array, /, *, dtype: Optional[dtype] = None, device: Optional[device] = None) -> array: +def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.zeros_like `. diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 03a857dfcfc..5ab611fd33f 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -4,12 +4,12 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import List, Tuple, Union, array, dtype + from ._types import List, Tuple, Union, Array, Dtype from collections.abc import Sequence import numpy as np -def broadcast_arrays(*arrays: Sequence[array]) -> List[array]: +def broadcast_arrays(*arrays: Sequence[Array]) -> List[Array]: """ Array API compatible wrapper for :py:func:`np.broadcast_arrays `. @@ -18,7 +18,7 @@ def broadcast_arrays(*arrays: Sequence[array]) -> List[array]: from ._array_object import ndarray return [ndarray._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays])] -def broadcast_to(x: array, /, shape: Tuple[int, ...]) -> array: +def broadcast_to(x: Array, /, shape: Tuple[int, ...]) -> Array: """ Array API compatible wrapper for :py:func:`np.broadcast_to `. @@ -27,7 +27,7 @@ def broadcast_to(x: array, /, shape: Tuple[int, ...]) -> array: from ._array_object import ndarray return ndarray._new(np.broadcast_to(x._array, shape)) -def can_cast(from_: Union[dtype, array], to: dtype, /) -> bool: +def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: """ Array API compatible wrapper for :py:func:`np.can_cast `. @@ -38,7 +38,7 @@ def can_cast(from_: Union[dtype, array], to: dtype, /) -> bool: from_ = from_._array return np.can_cast(from_, to) -def finfo(type: Union[dtype, array], /) -> finfo_object: +def finfo(type: Union[Dtype, Array], /) -> finfo_object: """ Array API compatible wrapper for :py:func:`np.finfo `. @@ -46,7 +46,7 @@ def finfo(type: Union[dtype, array], /) -> finfo_object: """ return np.finfo(type) -def iinfo(type: Union[dtype, array], /) -> iinfo_object: +def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: """ Array API compatible wrapper for :py:func:`np.iinfo `. @@ -54,7 +54,7 @@ def iinfo(type: Union[dtype, array], /) -> iinfo_object: """ return np.iinfo(type) -def result_type(*arrays_and_dtypes: Sequence[Union[array, dtype]]) -> dtype: +def result_type(*arrays_and_dtypes: Sequence[Union[Array, Dtype]]) -> Dtype: """ Array API compatible wrapper for :py:func:`np.result_type `. diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 197e773242a..ae265181a4e 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -7,11 +7,11 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import array + from ._types import Array import numpy as np -def abs(x: array, /) -> array: +def abs(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.abs `. @@ -23,7 +23,7 @@ def abs(x: array, /) -> array: # Note: the function name is different here @np.errstate(all='ignore') -def acos(x: array, /) -> array: +def acos(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arccos `. @@ -35,7 +35,7 @@ def acos(x: array, /) -> array: # Note: the function name is different here @np.errstate(all='ignore') -def acosh(x: array, /) -> array: +def acosh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arccosh `. @@ -46,7 +46,7 @@ def acosh(x: array, /) -> array: return ndarray._new(np.arccosh(x._array)) @np.errstate(all='ignore') -def add(x1: array, x2: array, /) -> array: +def add(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.add `. @@ -59,7 +59,7 @@ def add(x1: array, x2: array, /) -> array: # Note: the function name is different here @np.errstate(all='ignore') -def asin(x: array, /) -> array: +def asin(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arcsin `. @@ -71,7 +71,7 @@ def asin(x: array, /) -> array: # Note: the function name is different here @np.errstate(all='ignore') -def asinh(x: array, /) -> array: +def asinh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arcsinh `. @@ -82,7 +82,7 @@ def asinh(x: array, /) -> array: return ndarray._new(np.arcsinh(x._array)) # Note: the function name is different here -def atan(x: array, /) -> array: +def atan(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arctan `. @@ -93,7 +93,7 @@ def atan(x: array, /) -> array: return ndarray._new(np.arctan(x._array)) # Note: the function name is different here -def atan2(x1: array, x2: array, /) -> array: +def atan2(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arctan2 `. @@ -106,7 +106,7 @@ def atan2(x1: array, x2: array, /) -> array: # Note: the function name is different here @np.errstate(all='ignore') -def atanh(x: array, /) -> array: +def atanh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arctanh `. @@ -116,7 +116,7 @@ def atanh(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in atanh') return ndarray._new(np.arctanh(x._array)) -def bitwise_and(x1: array, x2: array, /) -> array: +def bitwise_and(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.bitwise_and `. @@ -128,7 +128,7 @@ def bitwise_and(x1: array, x2: array, /) -> array: return ndarray._new(np.bitwise_and(x1._array, x2._array)) # Note: the function name is different here -def bitwise_left_shift(x1: array, x2: array, /) -> array: +def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.left_shift `. @@ -146,7 +146,7 @@ def bitwise_left_shift(x1: array, x2: array, /) -> array: return ndarray._new(np.left_shift(x1._array, x2._array).astype(x1.dtype)) # Note: the function name is different here -def bitwise_invert(x: array, /) -> array: +def bitwise_invert(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.invert `. @@ -156,7 +156,7 @@ def bitwise_invert(x: array, /) -> array: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_invert') return ndarray._new(np.invert(x._array)) -def bitwise_or(x1: array, x2: array, /) -> array: +def bitwise_or(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.bitwise_or `. @@ -168,7 +168,7 @@ def bitwise_or(x1: array, x2: array, /) -> array: return ndarray._new(np.bitwise_or(x1._array, x2._array)) # Note: the function name is different here -def bitwise_right_shift(x1: array, x2: array, /) -> array: +def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.right_shift `. @@ -185,7 +185,7 @@ def bitwise_right_shift(x1: array, x2: array, /) -> array: # type promotion of the two input types. return ndarray._new(np.right_shift(x1._array, x2._array).astype(x1.dtype)) -def bitwise_xor(x1: array, x2: array, /) -> array: +def bitwise_xor(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.bitwise_xor `. @@ -196,7 +196,7 @@ def bitwise_xor(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.bitwise_xor(x1._array, x2._array)) -def ceil(x: array, /) -> array: +def ceil(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.ceil `. @@ -210,7 +210,7 @@ def ceil(x: array, /) -> array: return ndarray._new(np.ceil(x._array)) @np.errstate(all='ignore') -def cos(x: array, /) -> array: +def cos(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.cos `. @@ -221,7 +221,7 @@ def cos(x: array, /) -> array: return ndarray._new(np.cos(x._array)) @np.errstate(all='ignore') -def cosh(x: array, /) -> array: +def cosh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.cosh `. @@ -232,7 +232,7 @@ def cosh(x: array, /) -> array: return ndarray._new(np.cosh(x._array)) @np.errstate(all='ignore') -def divide(x1: array, x2: array, /) -> array: +def divide(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.divide `. @@ -243,7 +243,7 @@ def divide(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.divide(x1._array, x2._array)) -def equal(x1: array, x2: array, /) -> array: +def equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.equal `. @@ -253,7 +253,7 @@ def equal(x1: array, x2: array, /) -> array: return ndarray._new(np.equal(x1._array, x2._array)) @np.errstate(all='ignore') -def exp(x: array, /) -> array: +def exp(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.exp `. @@ -264,7 +264,7 @@ def exp(x: array, /) -> array: return ndarray._new(np.exp(x._array)) @np.errstate(all='ignore') -def expm1(x: array, /) -> array: +def expm1(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.expm1 `. @@ -274,7 +274,7 @@ def expm1(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in expm1') return ndarray._new(np.expm1(x._array)) -def floor(x: array, /) -> array: +def floor(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.floor `. @@ -288,7 +288,7 @@ def floor(x: array, /) -> array: return ndarray._new(np.floor(x._array)) @np.errstate(all='ignore') -def floor_divide(x1: array, x2: array, /) -> array: +def floor_divide(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.floor_divide `. @@ -299,7 +299,7 @@ def floor_divide(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.floor_divide(x1._array, x2._array)) -def greater(x1: array, x2: array, /) -> array: +def greater(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.greater `. @@ -310,7 +310,7 @@ def greater(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.greater(x1._array, x2._array)) -def greater_equal(x1: array, x2: array, /) -> array: +def greater_equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.greater_equal `. @@ -321,7 +321,7 @@ def greater_equal(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.greater_equal(x1._array, x2._array)) -def isfinite(x: array, /) -> array: +def isfinite(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.isfinite `. @@ -331,7 +331,7 @@ def isfinite(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in isfinite') return ndarray._new(np.isfinite(x._array)) -def isinf(x: array, /) -> array: +def isinf(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.isinf `. @@ -341,7 +341,7 @@ def isinf(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in isinf') return ndarray._new(np.isinf(x._array)) -def isnan(x: array, /) -> array: +def isnan(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.isnan `. @@ -351,7 +351,7 @@ def isnan(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in isnan') return ndarray._new(np.isnan(x._array)) -def less(x1: array, x2: array, /) -> array: +def less(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.less `. @@ -362,7 +362,7 @@ def less(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.less(x1._array, x2._array)) -def less_equal(x1: array, x2: array, /) -> array: +def less_equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.less_equal `. @@ -374,7 +374,7 @@ def less_equal(x1: array, x2: array, /) -> array: return ndarray._new(np.less_equal(x1._array, x2._array)) @np.errstate(all='ignore') -def log(x: array, /) -> array: +def log(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log `. @@ -385,7 +385,7 @@ def log(x: array, /) -> array: return ndarray._new(np.log(x._array)) @np.errstate(all='ignore') -def log1p(x: array, /) -> array: +def log1p(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log1p `. @@ -396,7 +396,7 @@ def log1p(x: array, /) -> array: return ndarray._new(np.log1p(x._array)) @np.errstate(all='ignore') -def log2(x: array, /) -> array: +def log2(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log2 `. @@ -407,7 +407,7 @@ def log2(x: array, /) -> array: return ndarray._new(np.log2(x._array)) @np.errstate(all='ignore') -def log10(x: array, /) -> array: +def log10(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log10 `. @@ -417,7 +417,7 @@ def log10(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in log10') return ndarray._new(np.log10(x._array)) -def logaddexp(x1: array, x2: array) -> array: +def logaddexp(x1: Array, x2: Array) -> Array: """ Array API compatible wrapper for :py:func:`np.logaddexp `. @@ -428,7 +428,7 @@ def logaddexp(x1: array, x2: array) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logaddexp(x1._array, x2._array)) -def logical_and(x1: array, x2: array, /) -> array: +def logical_and(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_and `. @@ -439,7 +439,7 @@ def logical_and(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logical_and(x1._array, x2._array)) -def logical_not(x: array, /) -> array: +def logical_not(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_not `. @@ -449,7 +449,7 @@ def logical_not(x: array, /) -> array: raise TypeError('Only boolean dtypes are allowed in logical_not') return ndarray._new(np.logical_not(x._array)) -def logical_or(x1: array, x2: array, /) -> array: +def logical_or(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_or `. @@ -460,7 +460,7 @@ def logical_or(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.logical_or(x1._array, x2._array)) -def logical_xor(x1: array, x2: array, /) -> array: +def logical_xor(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_xor `. @@ -472,7 +472,7 @@ def logical_xor(x1: array, x2: array, /) -> array: return ndarray._new(np.logical_xor(x1._array, x2._array)) @np.errstate(all='ignore') -def multiply(x1: array, x2: array, /) -> array: +def multiply(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.multiply `. @@ -483,7 +483,7 @@ def multiply(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.multiply(x1._array, x2._array)) -def negative(x: array, /) -> array: +def negative(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.negative `. @@ -493,7 +493,7 @@ def negative(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in negative') return ndarray._new(np.negative(x._array)) -def not_equal(x1: array, x2: array, /) -> array: +def not_equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.not_equal `. @@ -502,7 +502,7 @@ def not_equal(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.not_equal(x1._array, x2._array)) -def positive(x: array, /) -> array: +def positive(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.positive `. @@ -514,7 +514,7 @@ def positive(x: array, /) -> array: # Note: the function name is different here @np.errstate(all='ignore') -def pow(x1: array, x2: array, /) -> array: +def pow(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.power `. @@ -526,7 +526,7 @@ def pow(x1: array, x2: array, /) -> array: return ndarray._new(np.power(x1._array, x2._array)) @np.errstate(all='ignore') -def remainder(x1: array, x2: array, /) -> array: +def remainder(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.remainder `. @@ -537,7 +537,7 @@ def remainder(x1: array, x2: array, /) -> array: x1, x2 = ndarray._normalize_two_args(x1, x2) return ndarray._new(np.remainder(x1._array, x2._array)) -def round(x: array, /) -> array: +def round(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.round `. @@ -547,7 +547,7 @@ def round(x: array, /) -> array: raise TypeError('Only numeric dtypes are allowed in round') return ndarray._new(np.round(x._array)) -def sign(x: array, /) -> array: +def sign(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sign `. @@ -558,7 +558,7 @@ def sign(x: array, /) -> array: return ndarray._new(np.sign(x._array)) @np.errstate(all='ignore') -def sin(x: array, /) -> array: +def sin(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sin `. @@ -569,7 +569,7 @@ def sin(x: array, /) -> array: return ndarray._new(np.sin(x._array)) @np.errstate(all='ignore') -def sinh(x: array, /) -> array: +def sinh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sinh `. @@ -580,7 +580,7 @@ def sinh(x: array, /) -> array: return ndarray._new(np.sinh(x._array)) @np.errstate(all='ignore') -def square(x: array, /) -> array: +def square(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.square `. @@ -591,7 +591,7 @@ def square(x: array, /) -> array: return ndarray._new(np.square(x._array)) @np.errstate(all='ignore') -def sqrt(x: array, /) -> array: +def sqrt(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sqrt `. @@ -602,7 +602,7 @@ def sqrt(x: array, /) -> array: return ndarray._new(np.sqrt(x._array)) @np.errstate(all='ignore') -def subtract(x1: array, x2: array, /) -> array: +def subtract(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.subtract `. @@ -614,7 +614,7 @@ def subtract(x1: array, x2: array, /) -> array: return ndarray._new(np.subtract(x1._array, x2._array)) @np.errstate(all='ignore') -def tan(x: array, /) -> array: +def tan(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.tan `. @@ -624,7 +624,7 @@ def tan(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in tan') return ndarray._new(np.tan(x._array)) -def tanh(x: array, /) -> array: +def tanh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.tanh `. @@ -634,7 +634,7 @@ def tanh(x: array, /) -> array: raise TypeError('Only floating-point dtypes are allowed in tanh') return ndarray._new(np.tanh(x._array)) -def trunc(x: array, /) -> array: +def trunc(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.trunc `. diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index 4617706415c..b6b0c6f6e45 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Sequence, Tuple, Union, array + from ._types import Optional, Sequence, Tuple, Union, Array import numpy as np @@ -19,7 +19,7 @@ # """ # return np.einsum() -def matmul(x1: array, x2: array, /) -> array: +def matmul(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.matmul `. @@ -33,7 +33,7 @@ def matmul(x1: array, x2: array, /) -> array: return ndarray._new(np.matmul(x1._array, x2._array)) # Note: axes must be a tuple, unlike np.tensordot where it can be an array or array-like. -def tensordot(x1: array, x2: array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> array: +def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> Array: # Note: the restriction to numeric dtypes only is different from # np.tensordot. if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: @@ -41,7 +41,7 @@ def tensordot(x1: array, x2: array, /, *, axes: Union[int, Tuple[Sequence[int], return ndarray._new(np.tensordot(x1._array, x2._array, axes=axes)) -def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: +def transpose(x: Array, /, *, axes: Optional[Tuple[int, ...]] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.transpose `. @@ -50,7 +50,7 @@ def transpose(x: array, /, *, axes: Optional[Tuple[int, ...]] = None) -> array: return ndarray._new(np.transpose(x._array, axes=axes)) # Note: vecdot is not in NumPy -def vecdot(x1: array, x2: array, /, *, axis: Optional[int] = None) -> array: +def vecdot(x1: Array, x2: Array, /, *, axis: Optional[int] = None) -> Array: if axis is None: axis = -1 return tensordot(x1, x2, axes=((axis,), (axis,))) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 5f7b0a4515b..da02155f9f4 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -4,12 +4,12 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union, array + from ._types import Optional, Tuple, Union, Array import numpy as np # Note: the function name is different here -def concat(arrays: Tuple[array, ...], /, *, axis: Optional[int] = 0) -> array: +def concat(arrays: Tuple[Array, ...], /, *, axis: Optional[int] = 0) -> Array: """ Array API compatible wrapper for :py:func:`np.concatenate `. @@ -18,7 +18,7 @@ def concat(arrays: Tuple[array, ...], /, *, axis: Optional[int] = 0) -> array: arrays = tuple(a._array for a in arrays) return ndarray._new(np.concatenate(arrays, axis=axis)) -def expand_dims(x: array, /, *, axis: int) -> array: +def expand_dims(x: Array, /, *, axis: int) -> Array: """ Array API compatible wrapper for :py:func:`np.expand_dims `. @@ -26,7 +26,7 @@ def expand_dims(x: array, /, *, axis: int) -> array: """ return ndarray._new(np.expand_dims(x._array, axis)) -def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: +def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.flip `. @@ -34,7 +34,7 @@ def flip(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> """ return ndarray._new(np.flip(x._array, axis=axis)) -def reshape(x: array, /, shape: Tuple[int, ...]) -> array: +def reshape(x: Array, /, shape: Tuple[int, ...]) -> Array: """ Array API compatible wrapper for :py:func:`np.reshape `. @@ -42,7 +42,7 @@ def reshape(x: array, /, shape: Tuple[int, ...]) -> array: """ return ndarray._new(np.reshape(x._array, shape)) -def roll(x: array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: +def roll(x: Array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.roll `. @@ -50,7 +50,7 @@ def roll(x: array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Unio """ return ndarray._new(np.roll(x._array, shift, axis=axis)) -def squeeze(x: array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> array: +def squeeze(x: Array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.squeeze `. @@ -58,7 +58,7 @@ def squeeze(x: array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> """ return ndarray._new(np.squeeze(x._array, axis=axis)) -def stack(arrays: Tuple[array, ...], /, *, axis: int = 0) -> array: +def stack(arrays: Tuple[Array, ...], /, *, axis: int = 0) -> Array: """ Array API compatible wrapper for :py:func:`np.stack `. diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 9a5d583bcf7..69025643044 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Tuple, array + from ._types import Tuple, Array import numpy as np -def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: +def argmax(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.argmax `. @@ -17,7 +17,7 @@ def argmax(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: # Note: this currently fails as np.argmax does not implement keepdims return ndarray._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) -def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: +def argmin(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.argmin `. @@ -26,7 +26,7 @@ def argmin(x: array, /, *, axis: int = None, keepdims: bool = False) -> array: # Note: this currently fails as np.argmin does not implement keepdims return ndarray._new(np.asarray(np.argmin(x._array, axis=axis, keepdims=keepdims))) -def nonzero(x: array, /) -> Tuple[array, ...]: +def nonzero(x: Array, /) -> Tuple[Array, ...]: """ Array API compatible wrapper for :py:func:`np.nonzero `. @@ -34,7 +34,7 @@ def nonzero(x: array, /) -> Tuple[array, ...]: """ return ndarray._new(np.nonzero(x._array)) -def where(condition: array, x1: array, x2: array, /) -> array: +def where(condition: Array, x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.where `. diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 025a27d809a..719d54e5f51 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Tuple, Union, array + from ._types import Tuple, Union, Array import numpy as np -def unique(x: array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False) -> Union[array, Tuple[array, ...]]: +def unique(x: Array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False) -> Union[Array, Tuple[Array, ...]]: """ Array API compatible wrapper for :py:func:`np.unique `. diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 6e87bd90e1e..3dc0ec44471 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import array + from ._types import Array import numpy as np -def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> array: +def argsort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: """ Array API compatible wrapper for :py:func:`np.argsort `. @@ -21,7 +21,7 @@ def argsort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bo res = np.flip(res, axis=axis) return ndarray._new(res) -def sort(x: array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> array: +def sort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: """ Array API compatible wrapper for :py:func:`np.sort `. diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 26afd735454..e6a791fe65c 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -4,29 +4,29 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union, array + from ._types import Optional, Tuple, Union, Array import numpy as np -def max(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def max(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: return ndarray._new(np.max(x._array, axis=axis, keepdims=keepdims)) -def mean(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def mean(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: return ndarray._new(np.asarray(np.mean(x._array, axis=axis, keepdims=keepdims))) -def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def min(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: return ndarray._new(np.min(x._array, axis=axis, keepdims=keepdims)) -def prod(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def prod(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: return ndarray._new(np.asarray(np.prod(x._array, axis=axis, keepdims=keepdims))) -def std(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: +def std(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: # Note: the keyword argument correction is different here return ndarray._new(np.asarray(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims))) -def sum(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def sum(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: return ndarray._new(np.asarray(np.sum(x._array, axis=axis, keepdims=keepdims))) -def var(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> array: +def var(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: # Note: the keyword argument correction is different here return ndarray._new(np.asarray(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims))) diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index 1086699fcf2..602c1df3ebe 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -6,8 +6,8 @@ valid for inputs that match the given type annotations. """ -__all__ = ['Any', 'List', 'Literal', 'Optional', 'Tuple', 'Union', 'array', - 'device', 'dtype', 'SupportsDLPack', 'SupportsBufferProtocol', +__all__ = ['Any', 'List', 'Literal', 'Optional', 'Tuple', 'Union', 'Array', + 'Device', 'Dtype', 'SupportsDLPack', 'SupportsBufferProtocol', 'PyCapsule'] from typing import Any, List, Literal, Optional, Tuple, Union, TypeVar @@ -15,9 +15,9 @@ from . import (ndarray, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) -array = ndarray -device = TypeVar('device') -dtype = Literal[int8, int16, int32, int64, uint8, uint16, +Array = ndarray +Device = TypeVar('device') +Dtype = Literal[int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64] SupportsDLPack = TypeVar('SupportsDLPack') SupportsBufferProtocol = TypeVar('SupportsBufferProtocol') diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index e280b57853d..a6a7721dd27 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union, array + from ._types import Optional, Tuple, Union, Array import numpy as np -def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def all(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.all `. @@ -16,7 +16,7 @@ def all(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep """ return ndarray._new(np.asarray(np.all(x._array, axis=axis, keepdims=keepdims))) -def any(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> array: +def any(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.any `. From aee3a56d4e150a55c590966c9cc2ae0e201fa936 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 8 Jul 2021 17:22:47 -0600 Subject: [PATCH 077/151] Rename the array class in the array API namespace from ndarray to Array The actual class name doesn't matter because it isn't part of the namespace API (arrays should be constructed with the array creation functions like asarray()). However, it is better to use a name that is different from the existing NumPy array object to avoid ambiguity. --- numpy/_array_api/__init__.py | 10 +- numpy/_array_api/_array_object.py | 32 ++-- numpy/_array_api/_creation_functions.py | 84 ++++----- numpy/_array_api/_data_type_functions.py | 18 +- numpy/_array_api/_elementwise_functions.py | 164 +++++++++--------- numpy/_array_api/_linear_algebra_functions.py | 12 +- numpy/_array_api/_manipulation_functions.py | 18 +- numpy/_array_api/_searching_functions.py | 12 +- numpy/_array_api/_set_functions.py | 6 +- numpy/_array_api/_sorting_functions.py | 10 +- numpy/_array_api/_statistical_functions.py | 18 +- numpy/_array_api/_types.py | 2 +- numpy/_array_api/_utility_functions.py | 8 +- 13 files changed, 192 insertions(+), 202 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index e39a2c7d0f8..320c8df199b 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -58,7 +58,7 @@ guaranteed to give a comprehensive coverage of the spec. Therefore, those reviewing this submodule should refer to the standard documents themselves. -- There is a custom array object, numpy._array_api.ndarray, which is returned +- There is a custom array object, numpy._array_api.Array, which is returned by all functions in this module. All functions in the array API namespace implicitly assume that they will only receive this object as input. The only way to create instances of this object is to use one of the array creation @@ -69,14 +69,14 @@ limit/change certain behavior that differs in the spec. In particular: - Indexing: Only a subset of indices supported by NumPy are required by the - spec. The ndarray object restricts indexing to only allow those types of + spec. The Array object restricts indexing to only allow those types of indices that are required by the spec. See the docstring of the - numpy._array_api.ndarray._validate_indices helper function for more + numpy._array_api.Array._validate_indices helper function for more information. - Type promotion: Some type promotion rules are different in the spec. In particular, the spec does not have any value-based casting. Note that the - code to correct the type promotion rules on numpy._array_api.ndarray is + code to correct the type promotion rules on numpy._array_api.Array is not yet implemented. - All functions include type annotations, corresponding to those given in the @@ -93,7 +93,7 @@ Still TODO in this module are: -- Implement the spec type promotion rules on the ndarray object. +- Implement the spec type promotion rules on the Array object. - Disable NumPy warnings in the API functions. diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 89ec3ba1aac..9ea0eef1823 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -22,13 +22,13 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Any, Optional, PyCapsule, Tuple, Union, Array + from ._types import Any, Optional, PyCapsule, Tuple, Union, Device, Dtype import numpy as np -class ndarray: +class Array: """ - ndarray object for the array API namespace. + n-d array object for the array API namespace. See the docstring of :py:obj:`np.ndarray ` for more information. @@ -46,7 +46,7 @@ class ndarray: @classmethod def _new(cls, x, /): """ - This is a private method for initializing the array API ndarray + This is a private method for initializing the array API Array object. Functions outside of the array_api submodule should not use this @@ -64,9 +64,9 @@ def _new(cls, x, /): obj._array = x return obj - # Prevent ndarray() from working + # Prevent Array() from working def __new__(cls, *args, **kwargs): - raise TypeError("The array_api ndarray object should not be instantiated directly. Use an array creation function, such as asarray(), instead.") + raise TypeError("The array_api Array object should not be instantiated directly. Use an array creation function, such as asarray(), instead.") # These functions are not required by the spec, but are implemented for # the sake of usability. @@ -75,13 +75,13 @@ def __str__(self: Array, /) -> str: """ Performs the operation __str__. """ - return self._array.__str__().replace('array', 'ndarray') + return self._array.__str__().replace('array', 'Array') def __repr__(self: Array, /) -> str: """ Performs the operation __repr__. """ - return self._array.__repr__().replace('array', 'ndarray') + return self._array.__repr__().replace('array', 'Array') # Helper function to match the type promotion rules in the spec def _promote_scalar(self, scalar): @@ -109,7 +109,7 @@ def _promote_scalar(self, scalar): # behavior for integers within the bounds of the integer dtype. # Outside of those bounds we use the default NumPy behavior (either # cast or raise OverflowError). - return ndarray._new(np.array(scalar, self.dtype)) + return Array._new(np.array(scalar, self.dtype)) @staticmethod def _normalize_two_args(x1, x2): @@ -135,9 +135,9 @@ def _normalize_two_args(x1, x2): # performant. broadcast_to(x1._array, x2.shape) is much slower. We # could also manually type promote x2, but that is more complicated # and about the same performance as this. - x1 = ndarray._new(x1._array[None]) + x1 = Array._new(x1._array[None]) elif x2.shape == () and x1.shape != (): - x2 = ndarray._new(x2._array[None]) + x2 = Array._new(x2._array[None]) return (x1, x2) # Everything below this line is required by the spec. @@ -284,7 +284,7 @@ def _validate_index(key, shape): Additionally, it should be noted that indices that would return a scalar in NumPy will return a shape () array. Array scalars are not allowed in the specification, only shape () arrays. This is done in the - ``ndarray._new`` constructor, not this function. + ``Array._new`` constructor, not this function. """ if isinstance(key, slice): @@ -313,7 +313,7 @@ def _validate_index(key, shape): return key elif isinstance(key, tuple): - key = tuple(ndarray._validate_index(idx, None) for idx in key) + key = tuple(Array._validate_index(idx, None) for idx in key) for idx in key: if isinstance(idx, np.ndarray) and idx.dtype in _boolean_dtypes or isinstance(idx, (bool, np.bool_)): @@ -329,11 +329,11 @@ def _validate_index(key, shape): ellipsis_i = key.index(...) if n_ellipsis else len(key) for idx, size in list(zip(key[:ellipsis_i], shape)) + list(zip(key[:ellipsis_i:-1], shape[:ellipsis_i:-1])): - ndarray._validate_index(idx, (size,)) + Array._validate_index(idx, (size,)) return key elif isinstance(key, bool): return key - elif isinstance(key, ndarray): + elif isinstance(key, Array): if key.dtype in _integer_dtypes: if key.shape != (): raise IndexError("Integer array indices with shape != () are not allowed in the array API namespace") @@ -346,7 +346,7 @@ def _validate_index(key, shape): return operator.index(key) except TypeError: # Note: This also omits boolean arrays that are not already in - # ndarray() form, like a list of booleans. + # Array() form, like a list of booleans. raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") def __getitem__(self: Array, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], /) -> Array: diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 9845dd70faf..8fb2a8b12b3 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -19,14 +19,14 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su """ # _array_object imports in this file are inside the functions to avoid # circular imports - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") if copy is not None: # Note: copy is not yet implemented in np.asarray raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") - if isinstance(obj, ndarray) and (dtype is None or obj.dtype == dtype): + if isinstance(obj, Array) and (dtype is None or obj.dtype == dtype): return obj if dtype is None and isinstance(obj, int) and (obj > 2**64 or obj < -2**63): # Give a better error message in this case. NumPy would convert this @@ -35,7 +35,7 @@ def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, Su res = np.asarray(obj, dtype=dtype) if res.dtype not in _all_dtypes: raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") - return ndarray._new(res) + return Array._new(res) def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -43,11 +43,11 @@ def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.arange(start, stop=stop, step=step, dtype=dtype)) + return Array._new(np.arange(start, stop=stop, step=step, dtype=dtype)) def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -55,11 +55,11 @@ def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.empty(shape, dtype=dtype)) + return Array._new(np.empty(shape, dtype=dtype)) def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -67,11 +67,11 @@ def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.empty_like(x._array, dtype=dtype)) + return Array._new(np.empty_like(x._array, dtype=dtype)) def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -79,14 +79,14 @@ def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, d See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.eye(n_rows, M=n_cols, k=k, dtype=dtype)) + return Array._new(np.eye(n_rows, M=n_cols, k=k, dtype=dtype)) def from_dlpack(x: object, /) -> Array: - # Note: dlpack support is not yet implemented on ndarray + # Note: dlpack support is not yet implemented on Array raise NotImplementedError("DLPack support is not yet implemented") def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -95,18 +95,18 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - if isinstance(fill_value, ndarray) and fill_value.ndim == 0: - fill_value = fill_value._array[...] + if isinstance(fill_value, Array) and fill_value.ndim == 0: + fill_value = fill_value._array[...] res = np.full(shape, fill_value, dtype=dtype) if res.dtype not in _all_dtypes: # This will happen if the fill value is not something that NumPy # coerces to one of the acceptable dtypes. raise TypeError("Invalid input to full") - return ndarray._new(res) + return Array._new(res) def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -114,16 +114,16 @@ def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dty See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") res = np.full_like(x._array, fill_value, dtype=dtype) if res.dtype not in _all_dtypes: # This will happen if the fill value is not something that NumPy # coerces to one of the acceptable dtypes. raise TypeError("Invalid input to full_like") - return ndarray._new(res) + return Array._new(res) def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, endpoint: bool = True) -> Array: """ @@ -131,11 +131,11 @@ def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) + return Array._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) def meshgrid(*arrays: Sequence[Array], indexing: str = 'xy') -> List[Array, ...]: """ @@ -143,8 +143,8 @@ def meshgrid(*arrays: Sequence[Array], indexing: str = 'xy') -> List[Array, ...] See its docstring for more information. """ - from ._array_object import ndarray - return [ndarray._new(array) for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)] + from ._array_object import Array + return [Array._new(array) for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)] def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -152,11 +152,11 @@ def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, d See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.ones(shape, dtype=dtype)) + return Array._new(np.ones(shape, dtype=dtype)) def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -164,11 +164,11 @@ def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[De See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.ones_like(x._array, dtype=dtype)) + return Array._new(np.ones_like(x._array, dtype=dtype)) def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -176,11 +176,11 @@ def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.zeros(shape, dtype=dtype)) + return Array._new(np.zeros(shape, dtype=dtype)) def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: """ @@ -188,8 +188,8 @@ def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D See its docstring for more information. """ - from ._array_object import ndarray + from ._array_object import Array if device is not None: - # Note: Device support is not yet implemented on ndarray + # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") - return ndarray._new(np.zeros_like(x._array, dtype=dtype)) + return Array._new(np.zeros_like(x._array, dtype=dtype)) diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 5ab611fd33f..2f304bf49a6 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -1,10 +1,10 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import List, Tuple, Union, Array, Dtype + from ._types import List, Tuple, Union, Dtype from collections.abc import Sequence import numpy as np @@ -15,8 +15,8 @@ def broadcast_arrays(*arrays: Sequence[Array]) -> List[Array]: See its docstring for more information. """ - from ._array_object import ndarray - return [ndarray._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays])] + from ._array_object import Array + return [Array._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays])] def broadcast_to(x: Array, /, shape: Tuple[int, ...]) -> Array: """ @@ -24,8 +24,8 @@ def broadcast_to(x: Array, /, shape: Tuple[int, ...]) -> Array: See its docstring for more information. """ - from ._array_object import ndarray - return ndarray._new(np.broadcast_to(x._array, shape)) + from ._array_object import Array + return Array._new(np.broadcast_to(x._array, shape)) def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: """ @@ -33,8 +33,8 @@ def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: See its docstring for more information. """ - from ._array_object import ndarray - if isinstance(from_, ndarray): + from ._array_object import Array + if isinstance(from_, Array): from_ = from_._array return np.can_cast(from_, to) @@ -60,4 +60,4 @@ def result_type(*arrays_and_dtypes: Sequence[Union[Array, Dtype]]) -> Dtype: See its docstring for more information. """ - return np.result_type(*(a._array if isinstance(a, ndarray) else a for a in arrays_and_dtypes)) + return np.result_type(*(a._array if isinstance(a, Array) else a for a in arrays_and_dtypes)) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index ae265181a4e..8dedc77fbf5 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -3,11 +3,7 @@ from ._dtypes import (_boolean_dtypes, _floating_dtypes, _integer_dtypes, _integer_or_boolean_dtypes, _numeric_dtypes) -from ._array_object import ndarray - -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._types import Array +from ._array_object import Array import numpy as np @@ -19,7 +15,7 @@ def abs(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in abs') - return ndarray._new(np.abs(x._array)) + return Array._new(np.abs(x._array)) # Note: the function name is different here @np.errstate(all='ignore') @@ -31,7 +27,7 @@ def acos(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in acos') - return ndarray._new(np.arccos(x._array)) + return Array._new(np.arccos(x._array)) # Note: the function name is different here @np.errstate(all='ignore') @@ -43,7 +39,7 @@ def acosh(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in acosh') - return ndarray._new(np.arccosh(x._array)) + return Array._new(np.arccosh(x._array)) @np.errstate(all='ignore') def add(x1: Array, x2: Array, /) -> Array: @@ -54,8 +50,8 @@ def add(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in add') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.add(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.add(x1._array, x2._array)) # Note: the function name is different here @np.errstate(all='ignore') @@ -67,7 +63,7 @@ def asin(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in asin') - return ndarray._new(np.arcsin(x._array)) + return Array._new(np.arcsin(x._array)) # Note: the function name is different here @np.errstate(all='ignore') @@ -79,7 +75,7 @@ def asinh(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in asinh') - return ndarray._new(np.arcsinh(x._array)) + return Array._new(np.arcsinh(x._array)) # Note: the function name is different here def atan(x: Array, /) -> Array: @@ -90,7 +86,7 @@ def atan(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atan') - return ndarray._new(np.arctan(x._array)) + return Array._new(np.arctan(x._array)) # Note: the function name is different here def atan2(x1: Array, x2: Array, /) -> Array: @@ -101,8 +97,8 @@ def atan2(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atan2') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.arctan2(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.arctan2(x1._array, x2._array)) # Note: the function name is different here @np.errstate(all='ignore') @@ -114,7 +110,7 @@ def atanh(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atanh') - return ndarray._new(np.arctanh(x._array)) + return Array._new(np.arctanh(x._array)) def bitwise_and(x1: Array, x2: Array, /) -> Array: """ @@ -124,8 +120,8 @@ def bitwise_and(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer_or_boolean dtypes are allowed in bitwise_and') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.bitwise_and(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.bitwise_and(x1._array, x2._array)) # Note: the function name is different here def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: @@ -136,14 +132,14 @@ def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') - x1, x2 = ndarray._normalize_two_args(x1, x2) + x1, x2 = Array._normalize_two_args(x1, x2) # Note: bitwise_left_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): raise ValueError('bitwise_left_shift(x1, x2) is only defined for x2 >= 0') # Note: The spec requires the return dtype of bitwise_left_shift to be the # same as the first argument. np.left_shift() returns a type that is the # type promotion of the two input types. - return ndarray._new(np.left_shift(x1._array, x2._array).astype(x1.dtype)) + return Array._new(np.left_shift(x1._array, x2._array).astype(x1.dtype)) # Note: the function name is different here def bitwise_invert(x: Array, /) -> Array: @@ -154,7 +150,7 @@ def bitwise_invert(x: Array, /) -> Array: """ if x.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_invert') - return ndarray._new(np.invert(x._array)) + return Array._new(np.invert(x._array)) def bitwise_or(x1: Array, x2: Array, /) -> Array: """ @@ -164,8 +160,8 @@ def bitwise_or(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_or') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.bitwise_or(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.bitwise_or(x1._array, x2._array)) # Note: the function name is different here def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: @@ -176,14 +172,14 @@ def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') - x1, x2 = ndarray._normalize_two_args(x1, x2) + x1, x2 = Array._normalize_two_args(x1, x2) # Note: bitwise_right_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): raise ValueError('bitwise_right_shift(x1, x2) is only defined for x2 >= 0') # Note: The spec requires the return dtype of bitwise_left_shift to be the # same as the first argument. np.left_shift() returns a type that is the # type promotion of the two input types. - return ndarray._new(np.right_shift(x1._array, x2._array).astype(x1.dtype)) + return Array._new(np.right_shift(x1._array, x2._array).astype(x1.dtype)) def bitwise_xor(x1: Array, x2: Array, /) -> Array: """ @@ -193,8 +189,8 @@ def bitwise_xor(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_xor') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.bitwise_xor(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.bitwise_xor(x1._array, x2._array)) def ceil(x: Array, /) -> Array: """ @@ -207,7 +203,7 @@ def ceil(x: Array, /) -> Array: if x.dtype in _integer_dtypes: # Note: The return dtype of ceil is the same as the input return x - return ndarray._new(np.ceil(x._array)) + return Array._new(np.ceil(x._array)) @np.errstate(all='ignore') def cos(x: Array, /) -> Array: @@ -218,7 +214,7 @@ def cos(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in cos') - return ndarray._new(np.cos(x._array)) + return Array._new(np.cos(x._array)) @np.errstate(all='ignore') def cosh(x: Array, /) -> Array: @@ -229,7 +225,7 @@ def cosh(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in cosh') - return ndarray._new(np.cosh(x._array)) + return Array._new(np.cosh(x._array)) @np.errstate(all='ignore') def divide(x1: Array, x2: Array, /) -> Array: @@ -240,8 +236,8 @@ def divide(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in divide') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.divide(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.divide(x1._array, x2._array)) def equal(x1: Array, x2: Array, /) -> Array: """ @@ -249,8 +245,8 @@ def equal(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.equal(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.equal(x1._array, x2._array)) @np.errstate(all='ignore') def exp(x: Array, /) -> Array: @@ -261,7 +257,7 @@ def exp(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in exp') - return ndarray._new(np.exp(x._array)) + return Array._new(np.exp(x._array)) @np.errstate(all='ignore') def expm1(x: Array, /) -> Array: @@ -272,7 +268,7 @@ def expm1(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in expm1') - return ndarray._new(np.expm1(x._array)) + return Array._new(np.expm1(x._array)) def floor(x: Array, /) -> Array: """ @@ -285,7 +281,7 @@ def floor(x: Array, /) -> Array: if x.dtype in _integer_dtypes: # Note: The return dtype of floor is the same as the input return x - return ndarray._new(np.floor(x._array)) + return Array._new(np.floor(x._array)) @np.errstate(all='ignore') def floor_divide(x1: Array, x2: Array, /) -> Array: @@ -296,8 +292,8 @@ def floor_divide(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in floor_divide') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.floor_divide(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.floor_divide(x1._array, x2._array)) def greater(x1: Array, x2: Array, /) -> Array: """ @@ -307,8 +303,8 @@ def greater(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in greater') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.greater(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.greater(x1._array, x2._array)) def greater_equal(x1: Array, x2: Array, /) -> Array: """ @@ -318,8 +314,8 @@ def greater_equal(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in greater_equal') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.greater_equal(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.greater_equal(x1._array, x2._array)) def isfinite(x: Array, /) -> Array: """ @@ -329,7 +325,7 @@ def isfinite(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in isfinite') - return ndarray._new(np.isfinite(x._array)) + return Array._new(np.isfinite(x._array)) def isinf(x: Array, /) -> Array: """ @@ -339,7 +335,7 @@ def isinf(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in isinf') - return ndarray._new(np.isinf(x._array)) + return Array._new(np.isinf(x._array)) def isnan(x: Array, /) -> Array: """ @@ -349,7 +345,7 @@ def isnan(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in isnan') - return ndarray._new(np.isnan(x._array)) + return Array._new(np.isnan(x._array)) def less(x1: Array, x2: Array, /) -> Array: """ @@ -359,8 +355,8 @@ def less(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in less') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.less(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.less(x1._array, x2._array)) def less_equal(x1: Array, x2: Array, /) -> Array: """ @@ -370,8 +366,8 @@ def less_equal(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in less_equal') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.less_equal(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.less_equal(x1._array, x2._array)) @np.errstate(all='ignore') def log(x: Array, /) -> Array: @@ -382,7 +378,7 @@ def log(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in log') - return ndarray._new(np.log(x._array)) + return Array._new(np.log(x._array)) @np.errstate(all='ignore') def log1p(x: Array, /) -> Array: @@ -393,7 +389,7 @@ def log1p(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in log1p') - return ndarray._new(np.log1p(x._array)) + return Array._new(np.log1p(x._array)) @np.errstate(all='ignore') def log2(x: Array, /) -> Array: @@ -404,7 +400,7 @@ def log2(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in log2') - return ndarray._new(np.log2(x._array)) + return Array._new(np.log2(x._array)) @np.errstate(all='ignore') def log10(x: Array, /) -> Array: @@ -415,7 +411,7 @@ def log10(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in log10') - return ndarray._new(np.log10(x._array)) + return Array._new(np.log10(x._array)) def logaddexp(x1: Array, x2: Array) -> Array: """ @@ -425,8 +421,8 @@ def logaddexp(x1: Array, x2: Array) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in logaddexp') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.logaddexp(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.logaddexp(x1._array, x2._array)) def logical_and(x1: Array, x2: Array, /) -> Array: """ @@ -436,8 +432,8 @@ def logical_and(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_and') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.logical_and(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.logical_and(x1._array, x2._array)) def logical_not(x: Array, /) -> Array: """ @@ -447,7 +443,7 @@ def logical_not(x: Array, /) -> Array: """ if x.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_not') - return ndarray._new(np.logical_not(x._array)) + return Array._new(np.logical_not(x._array)) def logical_or(x1: Array, x2: Array, /) -> Array: """ @@ -457,8 +453,8 @@ def logical_or(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_or') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.logical_or(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.logical_or(x1._array, x2._array)) def logical_xor(x1: Array, x2: Array, /) -> Array: """ @@ -468,8 +464,8 @@ def logical_xor(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_xor') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.logical_xor(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.logical_xor(x1._array, x2._array)) @np.errstate(all='ignore') def multiply(x1: Array, x2: Array, /) -> Array: @@ -480,8 +476,8 @@ def multiply(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in multiply') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.multiply(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.multiply(x1._array, x2._array)) def negative(x: Array, /) -> Array: """ @@ -491,7 +487,7 @@ def negative(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in negative') - return ndarray._new(np.negative(x._array)) + return Array._new(np.negative(x._array)) def not_equal(x1: Array, x2: Array, /) -> Array: """ @@ -499,8 +495,8 @@ def not_equal(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.not_equal(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.not_equal(x1._array, x2._array)) def positive(x: Array, /) -> Array: """ @@ -510,7 +506,7 @@ def positive(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in positive') - return ndarray._new(np.positive(x._array)) + return Array._new(np.positive(x._array)) # Note: the function name is different here @np.errstate(all='ignore') @@ -522,8 +518,8 @@ def pow(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in pow') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.power(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.power(x1._array, x2._array)) @np.errstate(all='ignore') def remainder(x1: Array, x2: Array, /) -> Array: @@ -534,8 +530,8 @@ def remainder(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in remainder') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.remainder(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.remainder(x1._array, x2._array)) def round(x: Array, /) -> Array: """ @@ -545,7 +541,7 @@ def round(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in round') - return ndarray._new(np.round(x._array)) + return Array._new(np.round(x._array)) def sign(x: Array, /) -> Array: """ @@ -555,7 +551,7 @@ def sign(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in sign') - return ndarray._new(np.sign(x._array)) + return Array._new(np.sign(x._array)) @np.errstate(all='ignore') def sin(x: Array, /) -> Array: @@ -566,7 +562,7 @@ def sin(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in sin') - return ndarray._new(np.sin(x._array)) + return Array._new(np.sin(x._array)) @np.errstate(all='ignore') def sinh(x: Array, /) -> Array: @@ -577,7 +573,7 @@ def sinh(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in sinh') - return ndarray._new(np.sinh(x._array)) + return Array._new(np.sinh(x._array)) @np.errstate(all='ignore') def square(x: Array, /) -> Array: @@ -588,7 +584,7 @@ def square(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in square') - return ndarray._new(np.square(x._array)) + return Array._new(np.square(x._array)) @np.errstate(all='ignore') def sqrt(x: Array, /) -> Array: @@ -599,7 +595,7 @@ def sqrt(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in sqrt') - return ndarray._new(np.sqrt(x._array)) + return Array._new(np.sqrt(x._array)) @np.errstate(all='ignore') def subtract(x1: Array, x2: Array, /) -> Array: @@ -610,8 +606,8 @@ def subtract(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in subtract') - x1, x2 = ndarray._normalize_two_args(x1, x2) - return ndarray._new(np.subtract(x1._array, x2._array)) + x1, x2 = Array._normalize_two_args(x1, x2) + return Array._new(np.subtract(x1._array, x2._array)) @np.errstate(all='ignore') def tan(x: Array, /) -> Array: @@ -622,7 +618,7 @@ def tan(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in tan') - return ndarray._new(np.tan(x._array)) + return Array._new(np.tan(x._array)) def tanh(x: Array, /) -> Array: """ @@ -632,7 +628,7 @@ def tanh(x: Array, /) -> Array: """ if x.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in tanh') - return ndarray._new(np.tanh(x._array)) + return Array._new(np.tanh(x._array)) def trunc(x: Array, /) -> Array: """ @@ -642,4 +638,4 @@ def trunc(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in trunc') - return ndarray._new(np.trunc(x._array)) + return Array._new(np.trunc(x._array)) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index b6b0c6f6e45..b4b2af1343b 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,11 +1,9 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from ._dtypes import _numeric_dtypes -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._types import Optional, Sequence, Tuple, Union, Array +from typing import Optional, Sequence, Tuple, Union import numpy as np @@ -30,7 +28,7 @@ def matmul(x1: Array, x2: Array, /) -> Array: if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in matmul') - return ndarray._new(np.matmul(x1._array, x2._array)) + return Array._new(np.matmul(x1._array, x2._array)) # Note: axes must be a tuple, unlike np.tensordot where it can be an array or array-like. def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> Array: @@ -39,7 +37,7 @@ def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in tensordot') - return ndarray._new(np.tensordot(x1._array, x2._array, axes=axes)) + return Array._new(np.tensordot(x1._array, x2._array, axes=axes)) def transpose(x: Array, /, *, axes: Optional[Tuple[int, ...]] = None) -> Array: """ @@ -47,7 +45,7 @@ def transpose(x: Array, /, *, axes: Optional[Tuple[int, ...]] = None) -> Array: See its docstring for more information. """ - return ndarray._new(np.transpose(x._array, axes=axes)) + return Array._new(np.transpose(x._array, axes=axes)) # Note: vecdot is not in NumPy def vecdot(x1: Array, x2: Array, /, *, axis: Optional[int] = None) -> Array: diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index da02155f9f4..c569d283482 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,10 +1,10 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union, Array + from ._types import Optional, Tuple, Union import numpy as np @@ -16,7 +16,7 @@ def concat(arrays: Tuple[Array, ...], /, *, axis: Optional[int] = 0) -> Array: See its docstring for more information. """ arrays = tuple(a._array for a in arrays) - return ndarray._new(np.concatenate(arrays, axis=axis)) + return Array._new(np.concatenate(arrays, axis=axis)) def expand_dims(x: Array, /, *, axis: int) -> Array: """ @@ -24,7 +24,7 @@ def expand_dims(x: Array, /, *, axis: int) -> Array: See its docstring for more information. """ - return ndarray._new(np.expand_dims(x._array, axis)) + return Array._new(np.expand_dims(x._array, axis)) def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ @@ -32,7 +32,7 @@ def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> See its docstring for more information. """ - return ndarray._new(np.flip(x._array, axis=axis)) + return Array._new(np.flip(x._array, axis=axis)) def reshape(x: Array, /, shape: Tuple[int, ...]) -> Array: """ @@ -40,7 +40,7 @@ def reshape(x: Array, /, shape: Tuple[int, ...]) -> Array: See its docstring for more information. """ - return ndarray._new(np.reshape(x._array, shape)) + return Array._new(np.reshape(x._array, shape)) def roll(x: Array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ @@ -48,7 +48,7 @@ def roll(x: Array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Unio See its docstring for more information. """ - return ndarray._new(np.roll(x._array, shift, axis=axis)) + return Array._new(np.roll(x._array, shift, axis=axis)) def squeeze(x: Array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ @@ -56,7 +56,7 @@ def squeeze(x: Array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> See its docstring for more information. """ - return ndarray._new(np.squeeze(x._array, axis=axis)) + return Array._new(np.squeeze(x._array, axis=axis)) def stack(arrays: Tuple[Array, ...], /, *, axis: int = 0) -> Array: """ @@ -65,4 +65,4 @@ def stack(arrays: Tuple[Array, ...], /, *, axis: int = 0) -> Array: See its docstring for more information. """ arrays = tuple(a._array for a in arrays) - return ndarray._new(np.stack(arrays, axis=axis)) + return Array._new(np.stack(arrays, axis=axis)) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 69025643044..727f3013f69 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,10 +1,10 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Tuple, Array + from ._types import Tuple import numpy as np @@ -15,7 +15,7 @@ def argmax(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: See its docstring for more information. """ # Note: this currently fails as np.argmax does not implement keepdims - return ndarray._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) def argmin(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: """ @@ -24,7 +24,7 @@ def argmin(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: See its docstring for more information. """ # Note: this currently fails as np.argmin does not implement keepdims - return ndarray._new(np.asarray(np.argmin(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.argmin(x._array, axis=axis, keepdims=keepdims))) def nonzero(x: Array, /) -> Tuple[Array, ...]: """ @@ -32,7 +32,7 @@ def nonzero(x: Array, /) -> Tuple[Array, ...]: See its docstring for more information. """ - return ndarray._new(np.nonzero(x._array)) + return Array._new(np.nonzero(x._array)) def where(condition: Array, x1: Array, x2: Array, /) -> Array: """ @@ -40,4 +40,4 @@ def where(condition: Array, x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ - return ndarray._new(np.where(condition._array, x1._array, x2._array)) + return Array._new(np.where(condition._array, x1._array, x2._array)) diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 719d54e5f51..0981458666f 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -1,10 +1,10 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Tuple, Union, Array + from ._types import Tuple, Union import numpy as np @@ -14,4 +14,4 @@ def unique(x: Array, /, *, return_counts: bool = False, return_index: bool = Fal See its docstring for more information. """ - return ndarray._new(np.unique(x._array, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse)) + return Array._new(np.unique(x._array, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse)) diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/_array_api/_sorting_functions.py index 3dc0ec44471..a125e071832 100644 --- a/numpy/_array_api/_sorting_functions.py +++ b/numpy/_array_api/_sorting_functions.py @@ -1,10 +1,6 @@ from __future__ import annotations -from ._array_object import ndarray - -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._types import Array +from ._array_object import Array import numpy as np @@ -19,7 +15,7 @@ def argsort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bo res = np.argsort(x._array, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) - return ndarray._new(res) + return Array._new(res) def sort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: """ @@ -32,4 +28,4 @@ def sort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool res = np.sort(x._array, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) - return ndarray._new(res) + return Array._new(res) diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index e6a791fe65c..4f6b1c03445 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -1,32 +1,32 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union, Array + from ._types import Optional, Tuple, Union import numpy as np def max(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return ndarray._new(np.max(x._array, axis=axis, keepdims=keepdims)) + return Array._new(np.max(x._array, axis=axis, keepdims=keepdims)) def mean(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return ndarray._new(np.asarray(np.mean(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.mean(x._array, axis=axis, keepdims=keepdims))) def min(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return ndarray._new(np.min(x._array, axis=axis, keepdims=keepdims)) + return Array._new(np.min(x._array, axis=axis, keepdims=keepdims)) def prod(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return ndarray._new(np.asarray(np.prod(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.prod(x._array, axis=axis, keepdims=keepdims))) def std(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: # Note: the keyword argument correction is different here - return ndarray._new(np.asarray(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims))) + return Array._new(np.asarray(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims))) def sum(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return ndarray._new(np.asarray(np.sum(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.sum(x._array, axis=axis, keepdims=keepdims))) def var(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: # Note: the keyword argument correction is different here - return ndarray._new(np.asarray(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims))) + return Array._new(np.asarray(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims))) diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index 602c1df3ebe..d365e5e8ce6 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -12,7 +12,7 @@ from typing import Any, List, Literal, Optional, Tuple, Union, TypeVar -from . import (ndarray, int8, int16, int32, int64, uint8, uint16, uint32, +from . import (Array, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) Array = ndarray diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index a6a7721dd27..ba77d366890 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -1,10 +1,10 @@ from __future__ import annotations -from ._array_object import ndarray +from ._array_object import Array from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union, Array + from ._types import Optional, Tuple, Union import numpy as np @@ -14,7 +14,7 @@ def all(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return ndarray._new(np.asarray(np.all(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.all(x._array, axis=axis, keepdims=keepdims))) def any(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: """ @@ -22,4 +22,4 @@ def any(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep See its docstring for more information. """ - return ndarray._new(np.asarray(np.any(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.asarray(np.any(x._array, axis=axis, keepdims=keepdims))) From 4240314ed1e77e0b9d4546e654871274faca3e64 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 8 Jul 2021 17:42:35 -0600 Subject: [PATCH 078/151] Update the docstring of numpy/_array_api/__init__.py --- numpy/_array_api/__init__.py | 69 +++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index 320c8df199b..be8345759a9 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -1,6 +1,8 @@ """ A NumPy sub-namespace that conforms to the Python array API standard. +This submodule accompanies NEP 47, which proposes its inclusion in NumPy. + This is a proof-of-concept namespace that wraps the corresponding NumPy functions to give a conforming implementation of the Python array API standard (https://data-apis.github.io/array-api/latest/). The standard is currently in @@ -8,9 +10,6 @@ should be made either at https://github.com/data-apis/array-api or at https://github.com/data-apis/consortium-feedback/discussions. -This submodule will be accompanied with a NEP (not yet written) proposing its -inclusion in NumPy. - NumPy already follows the proposed spec for the most part, so this module serves mostly as a thin wrapper around it. However, NumPy also implements a lot of behavior that is not included in the spec, so this serves as a @@ -18,15 +17,19 @@ are included in this namespace, and all functions are given with the exact signature given in the spec, including the use of position-only arguments, and omitting any extra keyword arguments implemented by NumPy but not part of the -spec. Note that the array object itself is unchanged, as implementing a -restricted subclass of ndarray seems unnecessarily complex for the purposes of -this namespace, so the API of array methods and other behaviors of the array -object will include things that are not part of the spec. - -The spec is designed as a "minimal API subset" and explicitly allows libraries -to include behaviors not specified by it. But users of this module that intend -to write portable code should be aware that only those behaviors that are -listed in the spec are guaranteed to be implemented across libraries. +spec. The behavior of some functions is also modified from the NumPy behavior +to conform to the standard. Note that the underlying array object itself is +wrapped in a wrapper Array() class, but is otherwise unchanged. This submodule +is implemented in pure Python with no C extensions. + +The array API spec is designed as a "minimal API subset" and explicitly allows +libraries to include behaviors not specified by it. But users of this module +that intend to write portable code should be aware that only those behaviors +that are listed in the spec are guaranteed to be implemented across libraries. +Consequently, the NumPy implementation was chosen to be both conforming and +minimal, so that users can use this implementation of the array API namespace +and be sure that behaviors that it defines will be available in conforming +namespaces from other libraries. A few notes about the current state of this submodule: @@ -45,16 +48,10 @@ not included here, as it requires a full implementation in NumPy proper first. - - np.argmin and np.argmax do not implement the keepdims keyword argument. - - The linear algebra extension in the spec will be added in a future pull request. - - Some tests in the test suite are still not fully correct in that they test - all datatypes whereas certain functions are only defined for a subset of - datatypes. - - The test suite is yet complete, and even the tests that exist are not + The test suite is not yet complete, and even the tests that exist are not guaranteed to give a comprehensive coverage of the spec. Therefore, those reviewing this submodule should refer to the standard documents themselves. @@ -68,6 +65,10 @@ dtypes and only those methods that are required by the spec, as well as to limit/change certain behavior that differs in the spec. In particular: + - The array API namespace does not have scalar objects, only 0-d arrays. + Operations in on Array that would create a scalar in NumPy create a 0-d + array. + - Indexing: Only a subset of indices supported by NumPy are required by the spec. The Array object restricts indexing to only allow those types of indices that are required by the spec. See the docstring of the @@ -75,17 +76,27 @@ information. - Type promotion: Some type promotion rules are different in the spec. In - particular, the spec does not have any value-based casting. Note that the - code to correct the type promotion rules on numpy._array_api.Array is - not yet implemented. + particular, the spec does not have any value-based casting. The + Array._promote_scalar method promotes Python scalars to arrays, + disallowing cross-type promotions like int -> float64 that are not allowed + in the spec. Array._normalize_two_args works around some type promotion + quirks in NumPy, particularly, value-based casting that occurs when one + argument of an operation is a 0-d array. - All functions include type annotations, corresponding to those given in the - spec (see _types.py for definitions of the types 'array', 'device', and - 'dtype'). These do not currently fully pass mypy due to some limitations in - mypy. + spec (see _types.py for definitions of some custom types). These do not + currently fully pass mypy due to some limitations in mypy. + +- Dtype objects are just the NumPy dtype objects, e.g., float64 = + np.dtype('float64'). The spec does not require any behavior on these dtype + objects other than that they be accessible by name and be comparable by + equality, but it was considered too much extra complexity to create custom + objects to represent dtypes. - The wrapper functions in this module do not do any type checking for things - that would be impossible without leaving the _array_api namespace. + that would be impossible without leaving the _array_api namespace. For + example, since the array API dtype objects are just the NumPy dtype objects, + one could pass in a non-spec NumPy dtype into a function. - All places where the implementations in this submodule are known to deviate from their corresponding functions in NumPy are marked with "# Note" @@ -93,12 +104,6 @@ Still TODO in this module are: -- Implement the spec type promotion rules on the Array object. - -- Disable NumPy warnings in the API functions. - -- Implement keepdims on argmin and argmax. - - Device support and DLPack support are not yet implemented. These require support in NumPy itself first. From 5780a9bb5f662891da39eae80961455aaba0f6ac Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 13:56:34 -0600 Subject: [PATCH 079/151] Remove typing exports from numpy/_array_api/_types.py --- numpy/_array_api/_creation_functions.py | 7 +++---- numpy/_array_api/_manipulation_functions.py | 4 +--- numpy/_array_api/_types.py | 7 +++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 8fb2a8b12b3..9e9722a555a 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -1,11 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional, Tuple, Union if TYPE_CHECKING: - from ._types import (List, Optional, SupportsDLPack, - SupportsBufferProtocol, Tuple, Union, Array, Device, - Dtype) + from ._types import (NestedSequence, SupportsDLPack, + SupportsBufferProtocol, Array, Device, Dtype) from collections.abc import Sequence from ._dtypes import _all_dtypes diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index c569d283482..fa0c08d7bec 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -2,9 +2,7 @@ from ._array_object import Array -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._types import Optional, Tuple, Union +from typing import List, Optional, Tuple, Union import numpy as np diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index d365e5e8ce6..050ad4031be 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -6,11 +6,10 @@ valid for inputs that match the given type annotations. """ -__all__ = ['Any', 'List', 'Literal', 'Optional', 'Tuple', 'Union', 'Array', - 'Device', 'Dtype', 'SupportsDLPack', 'SupportsBufferProtocol', - 'PyCapsule'] +__all__ = ['Array', 'Device', 'Dtype', 'SupportsDLPack', + 'SupportsBufferProtocol', 'PyCapsule'] -from typing import Any, List, Literal, Optional, Tuple, Union, TypeVar +from typing import Any, Sequence, Type, Union from . import (Array, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) From 29b7a69a39ac66ebd8f61c6c9c65e7e60b40b4a0 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 13:57:01 -0600 Subject: [PATCH 080/151] Use better type definitions for the array API custom types --- numpy/_array_api/_types.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_types.py index 050ad4031be..4ff718205dd 100644 --- a/numpy/_array_api/_types.py +++ b/numpy/_array_api/_types.py @@ -14,10 +14,13 @@ from . import (Array, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64) -Array = ndarray -Device = TypeVar('device') -Dtype = Literal[int8, int16, int32, int64, uint8, uint16, - uint32, uint64, float32, float64] -SupportsDLPack = TypeVar('SupportsDLPack') -SupportsBufferProtocol = TypeVar('SupportsBufferProtocol') -PyCapsule = TypeVar('PyCapsule') +# This should really be recursive, but that isn't supported yet. See the +# similar comment in numpy/typing/_array_like.py +NestedSequence = Sequence[Sequence[Any]] + +Device = Any +Dtype = Type[Union[[int8, int16, int32, int64, uint8, uint16, + uint32, uint64, float32, float64]]] +SupportsDLPack = Any +SupportsBufferProtocol = Any +PyCapsule = Any From 74478e2d943f4d61917d4d9122a042214eed94fd Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 13:57:44 -0600 Subject: [PATCH 081/151] Use better type signatures in the array API module This includes returning custom dataclasses for finfo and iinfo that only contain the properties required by the array API specification. --- numpy/_array_api/_array_object.py | 15 +++++---- numpy/_array_api/_creation_functions.py | 2 +- numpy/_array_api/_data_type_functions.py | 37 +++++++++++++++++++-- numpy/_array_api/_manipulation_functions.py | 4 +-- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 9ea0eef1823..43d8a89619c 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -396,7 +396,8 @@ def __le__(self: Array, other: Union[int, float, Array], /) -> Array: res = self._array.__le__(other._array) return self.__class__._new(res) - def __len__(self, /): + # Note: __len__ may end up being removed from the array API spec. + def __len__(self, /) -> int: """ Performs the operation __len__. """ @@ -843,7 +844,7 @@ def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array: return self.__class__._new(res) @property - def dtype(self): + def dtype(self) -> Dtype: """ Array API compatible wrapper for :py:meth:`np.ndaray.dtype `. @@ -852,7 +853,7 @@ def dtype(self): return self._array.dtype @property - def device(self): + def device(self) -> Device: """ Array API compatible wrapper for :py:meth:`np.ndaray.device `. @@ -862,7 +863,7 @@ def device(self): raise NotImplementedError("The device attribute is not yet implemented") @property - def ndim(self): + def ndim(self) -> int: """ Array API compatible wrapper for :py:meth:`np.ndaray.ndim `. @@ -871,7 +872,7 @@ def ndim(self): return self._array.ndim @property - def shape(self): + def shape(self) -> Tuple[int, ...]: """ Array API compatible wrapper for :py:meth:`np.ndaray.shape `. @@ -880,7 +881,7 @@ def shape(self): return self._array.shape @property - def size(self): + def size(self) -> int: """ Array API compatible wrapper for :py:meth:`np.ndaray.size `. @@ -889,7 +890,7 @@ def size(self): return self._array.size @property - def T(self): + def T(self) -> Array: """ Array API compatible wrapper for :py:meth:`np.ndaray.T `. diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 9e9722a555a..517c2bfddc6 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -10,7 +10,7 @@ import numpy as np -def asarray(obj: Union[float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: +def asarray(obj: Union[Array, float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.asarray `. diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 2f304bf49a6..693ceae84f2 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -2,6 +2,7 @@ from ._array_object import Array +from dataclasses import dataclass from typing import TYPE_CHECKING if TYPE_CHECKING: from ._types import List, Tuple, Union, Dtype @@ -38,13 +39,44 @@ def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: from_ = from_._array return np.can_cast(from_, to) +# These are internal objects for the return types of finfo and iinfo, since +# the NumPy versions contain extra data that isn't part of the spec. +@dataclass +class finfo_object: + bits: int + # Note: The types of the float data here are float, whereas in NumPy they + # are scalars of the corresponding float dtype. + eps: float + max: float + min: float + # Note: smallest_normal is part of the array API spec, but cannot be used + # until https://github.com/numpy/numpy/pull/18536 is merged. + + # smallest_normal: float + +@dataclass +class iinfo_object: + bits: int + max: int + min: int + def finfo(type: Union[Dtype, Array], /) -> finfo_object: """ Array API compatible wrapper for :py:func:`np.finfo `. See its docstring for more information. """ - return np.finfo(type) + fi = np.finfo(type) + # Note: The types of the float data here are float, whereas in NumPy they + # are scalars of the corresponding float dtype. + return finfo_object( + fi.bits, + float(fi.eps), + float(fi.max), + float(fi.min), + # TODO: Uncomment this when #18536 is merged. + # float(fi.smallest_normal), + ) def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: """ @@ -52,7 +84,8 @@ def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: See its docstring for more information. """ - return np.iinfo(type) + ii = np.iinfo(type) + return iinfo_object(ii.bits, ii.max, ii.min) def result_type(*arrays_and_dtypes: Sequence[Union[Array, Dtype]]) -> Dtype: """ diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index fa0c08d7bec..6308bfc2691 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -7,7 +7,7 @@ import numpy as np # Note: the function name is different here -def concat(arrays: Tuple[Array, ...], /, *, axis: Optional[int] = 0) -> Array: +def concat(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[int] = 0) -> Array: """ Array API compatible wrapper for :py:func:`np.concatenate `. @@ -56,7 +56,7 @@ def squeeze(x: Array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> """ return Array._new(np.squeeze(x._array, axis=axis)) -def stack(arrays: Tuple[Array, ...], /, *, axis: int = 0) -> Array: +def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) -> Array: """ Array API compatible wrapper for :py:func:`np.stack `. From 60add4a3ebabcc1e8dae07c4c11a65f56607f708 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 13:58:30 -0600 Subject: [PATCH 082/151] Small code cleanup --- numpy/_array_api/_array_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 43d8a89619c..404a096549c 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -357,7 +357,7 @@ def __getitem__(self: Array, key: Union[int, slice, ellipsis, Tuple[Union[int, s # docstring of _validate_index key = self._validate_index(key, self.shape) res = self._array.__getitem__(key) - return self.__class__._new(res) + return self._new(res) def __gt__(self: Array, other: Union[int, float, Array], /) -> Array: """ From 6379138a6da6ebf73bfc4bc4e019a21d8a99be0a Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 14:00:34 -0600 Subject: [PATCH 083/151] Rename numpy/_array_api/_types.py to numpy/_array_api/_typing.py --- numpy/_array_api/__init__.py | 2 +- numpy/_array_api/_array_object.py | 2 +- numpy/_array_api/_creation_functions.py | 2 +- numpy/_array_api/_data_type_functions.py | 2 +- numpy/_array_api/_searching_functions.py | 2 +- numpy/_array_api/_set_functions.py | 2 +- numpy/_array_api/_statistical_functions.py | 2 +- numpy/_array_api/{_types.py => _typing.py} | 0 numpy/_array_api/_utility_functions.py | 2 +- 9 files changed, 8 insertions(+), 8 deletions(-) rename numpy/_array_api/{_types.py => _typing.py} (100%) diff --git a/numpy/_array_api/__init__.py b/numpy/_array_api/__init__.py index be8345759a9..57a4ff4e192 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/_array_api/__init__.py @@ -84,7 +84,7 @@ argument of an operation is a 0-d array. - All functions include type annotations, corresponding to those given in the - spec (see _types.py for definitions of some custom types). These do not + spec (see _typing.py for definitions of some custom types). These do not currently fully pass mypy due to some limitations in mypy. - Dtype objects are just the NumPy dtype objects, e.g., float64 = diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 404a096549c..2377bffe376 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Any, Optional, PyCapsule, Tuple, Union, Device, Dtype + from ._typing import Any, Optional, PyCapsule, Tuple, Union, Device, Dtype import numpy as np diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 517c2bfddc6..88b3808b457 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, Union if TYPE_CHECKING: - from ._types import (NestedSequence, SupportsDLPack, + from ._typing import (NestedSequence, SupportsDLPack, SupportsBufferProtocol, Array, Device, Dtype) from collections.abc import Sequence from ._dtypes import _all_dtypes diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 693ceae84f2..0c42386b5d9 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import List, Tuple, Union, Dtype + from ._typing import List, Tuple, Union, Dtype from collections.abc import Sequence import numpy as np diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 727f3013f69..c96f258bb50 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Tuple + from ._typing import Tuple import numpy as np diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 0981458666f..40d4895bf1f 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Tuple, Union + from ._typing import Tuple, Union import numpy as np diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 4f6b1c03445..9e032adf0af 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union + from ._typing import Optional, Tuple, Union import numpy as np diff --git a/numpy/_array_api/_types.py b/numpy/_array_api/_typing.py similarity index 100% rename from numpy/_array_api/_types.py rename to numpy/_array_api/_typing.py diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index ba77d366890..3a387877ecd 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from ._types import Optional, Tuple, Union + from ._typing import Optional, Tuple, Union import numpy as np From 5febef530e055572fd5eac18807675ee451c81b0 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 16:24:45 -0600 Subject: [PATCH 084/151] Only allow floating-point dtypes in the array API __pow__ and __truediv__ See https://github.com/data-apis/array-api/pull/221. --- numpy/_array_api/_array_object.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 2377bffe376..797f9ea4f43 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -503,6 +503,8 @@ def __pow__(self: Array, other: Union[int, float, Array], /) -> Array: if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) + if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in __pow__') # Note: NumPy's __pow__ does not follow type promotion rules for 0-d # arrays, so we use pow() here instead. return pow(self, other) @@ -548,6 +550,8 @@ def __truediv__(self: Array, other: Union[int, float, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) + if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in __truediv__') self, other = self._normalize_two_args(self, other) res = self._array.__truediv__(other._array) return self.__class__._new(res) @@ -744,6 +748,8 @@ def __ipow__(self: Array, other: Union[int, float, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) + if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in __pow__') self._array.__ipow__(other._array) return self @@ -756,6 +762,8 @@ def __rpow__(self: Array, other: Union[int, float, Array], /) -> Array: if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) + if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in __pow__') # Note: NumPy's __pow__ does not follow the spec type promotion rules # for 0-d arrays, so we use pow() here instead. return pow(other, self) @@ -810,6 +818,8 @@ def __itruediv__(self: Array, other: Union[int, float, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) + if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in __truediv__') self._array.__itruediv__(other._array) return self @@ -820,6 +830,8 @@ def __rtruediv__(self: Array, other: Union[int, float, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) + if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: + raise TypeError('Only floating-point dtypes are allowed in __truediv__') self, other = self._normalize_two_args(self, other) res = self._array.__rtruediv__(other._array) return self.__class__._new(res) From c5999e2163f06bb9316dab03d0e1b2173e78bb65 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 16:28:13 -0600 Subject: [PATCH 085/151] Update the type hints for the array API __pow__ and __truediv__ They should not accept int. PEP 484 actually makes int a subtype of float, so this won't actually affect type checkers the way we would want. --- numpy/_array_api/_array_object.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 797f9ea4f43..a5806369820 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -494,8 +494,10 @@ def __pos__(self: Array, /) -> Array: res = self._array.__pos__() return self.__class__._new(res) + # PEP 484 requires int to be a subtype of float, but __pow__ should not + # accept int. @np.errstate(all='ignore') - def __pow__(self: Array, other: Union[int, float, Array], /) -> Array: + def __pow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __pow__. """ @@ -543,8 +545,10 @@ def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: res = self._array.__sub__(other._array) return self.__class__._new(res) + # PEP 484 requires int to be a subtype of float, but __truediv__ should + # not accept int. @np.errstate(all='ignore') - def __truediv__(self: Array, other: Union[int, float, Array], /) -> Array: + def __truediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __truediv__. """ @@ -742,7 +746,7 @@ def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array: return self.__class__._new(res) @np.errstate(all='ignore') - def __ipow__(self: Array, other: Union[int, float, Array], /) -> Array: + def __ipow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __ipow__. """ @@ -754,7 +758,7 @@ def __ipow__(self: Array, other: Union[int, float, Array], /) -> Array: return self @np.errstate(all='ignore') - def __rpow__(self: Array, other: Union[int, float, Array], /) -> Array: + def __rpow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __rpow__. """ @@ -812,7 +816,7 @@ def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: return self.__class__._new(res) @np.errstate(all='ignore') - def __itruediv__(self: Array, other: Union[int, float, Array], /) -> Array: + def __itruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __itruediv__. """ @@ -824,7 +828,7 @@ def __itruediv__(self: Array, other: Union[int, float, Array], /) -> Array: return self @np.errstate(all='ignore') - def __rtruediv__(self: Array, other: Union[int, float, Array], /) -> Array: + def __rtruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __rtruediv__. """ From 29974fba6810e1be7e8a2ba8322bd8c78a9012d0 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 16:32:42 -0600 Subject: [PATCH 086/151] Use tuples for internal type lists in the array API These are easier for type checkers to handle. --- numpy/_array_api/_dtypes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index c874763ddb3..1bec88c3615 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -15,10 +15,10 @@ # Note: This name is changed bool = np.dtype('bool') -_all_dtypes = [int8, int16, int32, int64, uint8, uint16, uint32, uint64, - float32, float64, bool] -_boolean_dtypes = [bool] -_floating_dtypes = [float32, float64] -_integer_dtypes = [int8, int16, int32, int64, uint8, uint16, uint32, uint64] -_integer_or_boolean_dtypes = [bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64] -_numeric_dtypes = [float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64] +_all_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64, bool) +_boolean_dtypes = (bool) +_floating_dtypes = (float32, float64) +_integer_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64) +_integer_or_boolean_dtypes = (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64) +_numeric_dtypes = (float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64) From 8ca96b2c949d23dd9fbfc7896845aa79dd2f0181 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 16:38:08 -0600 Subject: [PATCH 087/151] Fix some typing imports --- numpy/_array_api/_array_object.py | 4 ++-- numpy/_array_api/_creation_functions.py | 4 ++-- numpy/_array_api/_data_type_functions.py | 4 ++-- numpy/_array_api/_searching_functions.py | 4 +--- numpy/_array_api/_set_functions.py | 4 +--- numpy/_array_api/_statistical_functions.py | 4 +--- numpy/_array_api/_utility_functions.py | 4 +--- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index a5806369820..0e0544afea4 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -20,9 +20,9 @@ from ._creation_functions import asarray from ._dtypes import _boolean_dtypes, _integer_dtypes, _floating_dtypes -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional, Tuple, Union if TYPE_CHECKING: - from ._typing import Any, Optional, PyCapsule, Tuple, Union, Device, Dtype + from ._typing import PyCapsule, Device, Dtype import numpy as np diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 88b3808b457..2be0aea09ec 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -3,8 +3,8 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, Union if TYPE_CHECKING: - from ._typing import (NestedSequence, SupportsDLPack, - SupportsBufferProtocol, Array, Device, Dtype) + from ._typing import (Array, Device, Dtype, NestedSequence, + SupportsDLPack, SupportsBufferProtocol) from collections.abc import Sequence from ._dtypes import _all_dtypes diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 0c42386b5d9..fe5c7557f9c 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -3,9 +3,9 @@ from ._array_object import Array from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Tuple, Union if TYPE_CHECKING: - from ._typing import List, Tuple, Union, Dtype + from ._typing import Dtype from collections.abc import Sequence import numpy as np diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index c96f258bb50..0c2bbd73758 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -2,9 +2,7 @@ from ._array_object import Array -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._typing import Tuple +from typing import Optional, Tuple import numpy as np diff --git a/numpy/_array_api/_set_functions.py b/numpy/_array_api/_set_functions.py index 40d4895bf1f..f28c2ee7284 100644 --- a/numpy/_array_api/_set_functions.py +++ b/numpy/_array_api/_set_functions.py @@ -2,9 +2,7 @@ from ._array_object import Array -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._typing import Tuple, Union +from typing import Tuple, Union import numpy as np diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/_array_api/_statistical_functions.py index 9e032adf0af..61fc60c46f2 100644 --- a/numpy/_array_api/_statistical_functions.py +++ b/numpy/_array_api/_statistical_functions.py @@ -2,9 +2,7 @@ from ._array_object import Array -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._typing import Optional, Tuple, Union +from typing import Optional, Tuple, Union import numpy as np diff --git a/numpy/_array_api/_utility_functions.py b/numpy/_array_api/_utility_functions.py index 3a387877ecd..f243bfe684d 100644 --- a/numpy/_array_api/_utility_functions.py +++ b/numpy/_array_api/_utility_functions.py @@ -2,9 +2,7 @@ from ._array_object import Array -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._typing import Optional, Tuple, Union +from typing import Optional, Tuple, Union import numpy as np From 639aa7cd7ce02b741376c6c10226a6014310fc0b Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 16:39:56 -0600 Subject: [PATCH 088/151] Fix the type hints for argmin/argmax in the array API --- numpy/_array_api/_searching_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 0c2bbd73758..4764992a156 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -6,7 +6,7 @@ import numpy as np -def argmax(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: +def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.argmax `. @@ -15,7 +15,7 @@ def argmax(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: # Note: this currently fails as np.argmax does not implement keepdims return Array._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) -def argmin(x: Array, /, *, axis: int = None, keepdims: bool = False) -> Array: +def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.argmin `. From 6765494edee2b90f239ae622abb5f3a7d218aa84 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 17:58:39 -0600 Subject: [PATCH 089/151] Fix typo --- numpy/_array_api/_dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index 1bec88c3615..b183c800f7a 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -17,7 +17,7 @@ _all_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool) -_boolean_dtypes = (bool) +_boolean_dtypes = (bool,) _floating_dtypes = (float32, float64) _integer_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64) _integer_or_boolean_dtypes = (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64) From 5217236995f839250a148e533f4395007288cc24 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 9 Jul 2021 18:08:22 -0600 Subject: [PATCH 090/151] Make the array API left and right shift do type promotion The spec previously said it should return the type of the left argument, but this was changed to do type promotion to be consistent with all the other elementwise functions/operators. --- numpy/_array_api/_array_object.py | 28 +++++++--------------- numpy/_array_api/_elementwise_functions.py | 10 ++------ 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 0e0544afea4..6b9647626de 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -410,11 +410,8 @@ def __lshift__(self: Array, other: Union[int, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - # Note: The spec requires the return dtype of bitwise_left_shift, and - # hence also __lshift__, to be the same as the first argument. - # np.ndarray.__lshift__ returns a type that is the type promotion of - # the two input types. - res = self._array.__lshift__(other._array).astype(self.dtype) + self, other = self._normalize_two_args(self, other) + res = self._array.__lshift__(other._array) return self.__class__._new(res) def __lt__(self: Array, other: Union[int, float, Array], /) -> Array: @@ -517,11 +514,8 @@ def __rshift__(self: Array, other: Union[int, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - # Note: The spec requires the return dtype of bitwise_right_shift, and - # hence also __rshift__, to be the same as the first argument. - # np.ndarray.__rshift__ returns a type that is the type promotion of - # the two input types. - res = self._array.__rshift__(other._array).astype(self.dtype) + self, other = self._normalize_two_args(self, other) + res = self._array.__rshift__(other._array) return self.__class__._new(res) def __setitem__(self, key, value, /): @@ -646,11 +640,8 @@ def __rlshift__(self: Array, other: Union[int, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - # Note: The spec requires the return dtype of bitwise_left_shift, and - # hence also __lshift__, to be the same as the first argument. - # np.ndarray.__lshift__ returns a type that is the type promotion of - # the two input types. - res = self._array.__rlshift__(other._array).astype(other.dtype) + self, other = self._normalize_two_args(self, other) + res = self._array.__rlshift__(other._array) return self.__class__._new(res) def __imatmul__(self: Array, other: Array, /) -> Array: @@ -787,11 +778,8 @@ def __rrshift__(self: Array, other: Union[int, Array], /) -> Array: """ if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) - # Note: The spec requires the return dtype of bitwise_right_shift, and - # hence also __rshift__, to be the same as the first argument. - # np.ndarray.__rshift__ returns a type that is the type promotion of - # the two input types. - res = self._array.__rrshift__(other._array).astype(other.dtype) + self, other = self._normalize_two_args(self, other) + res = self._array.__rrshift__(other._array) return self.__class__._new(res) @np.errstate(all='ignore') diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 8dedc77fbf5..0d955b614a8 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -136,10 +136,7 @@ def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: # Note: bitwise_left_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): raise ValueError('bitwise_left_shift(x1, x2) is only defined for x2 >= 0') - # Note: The spec requires the return dtype of bitwise_left_shift to be the - # same as the first argument. np.left_shift() returns a type that is the - # type promotion of the two input types. - return Array._new(np.left_shift(x1._array, x2._array).astype(x1.dtype)) + return Array._new(np.left_shift(x1._array, x2._array)) # Note: the function name is different here def bitwise_invert(x: Array, /) -> Array: @@ -176,10 +173,7 @@ def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: # Note: bitwise_right_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): raise ValueError('bitwise_right_shift(x1, x2) is only defined for x2 >= 0') - # Note: The spec requires the return dtype of bitwise_left_shift to be the - # same as the first argument. np.left_shift() returns a type that is the - # type promotion of the two input types. - return Array._new(np.right_shift(x1._array, x2._array).astype(x1.dtype)) + return Array._new(np.right_shift(x1._array, x2._array)) def bitwise_xor(x1: Array, x2: Array, /) -> Array: """ From 2bdc5c33b1e9eb394eb62533f4ae4df081ea1452 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 14:19:17 -0600 Subject: [PATCH 091/151] Make the _array_api submodule install correctly --- numpy/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/numpy/setup.py b/numpy/setup.py index cbf633504db..82c4c8d1b75 100644 --- a/numpy/setup.py +++ b/numpy/setup.py @@ -4,6 +4,7 @@ def configuration(parent_package='',top_path=None): from numpy.distutils.misc_util import Configuration config = Configuration('numpy', parent_package, top_path) + config.add_subpackage('_array_api') config.add_subpackage('compat') config.add_subpackage('core') config.add_subpackage('distutils') From 49bd66076439a1de85c9924d800546973c857f95 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 16:27:59 -0600 Subject: [PATCH 092/151] Use ndim == 0 instead of shape == () in the array API code --- numpy/_array_api/_array_object.py | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 6b9647626de..7b5531b9b76 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -55,9 +55,9 @@ def _new(cls, x, /): """ obj = super().__new__(cls) - # Note: The spec does not have array scalars, only shape () arrays. + # Note: The spec does not have array scalars, only 0-D arrays. if isinstance(x, np.generic): - # Convert the array scalar to a shape () array + # Convert the array scalar to a 0-D array xa = np.empty((), x.dtype) xa[()] = x x = xa @@ -130,13 +130,13 @@ def _normalize_two_args(x1, x2): broadcasting, so the resulting shape is the same, but this prevents NumPy from not promoting the dtype. """ - if x1.shape == () and x2.shape != (): + if x1.ndim == 0 and x2.ndim != 0: # The _array[None] workaround was chosen because it is relatively # performant. broadcast_to(x1._array, x2.shape) is much slower. We # could also manually type promote x2, but that is more complicated # and about the same performance as this. x1 = Array._new(x1._array[None]) - elif x2.shape == () and x1.shape != (): + elif x2.ndim == 0 and x1.ndim != 0: x2 = Array._new(x2._array[None]) return (x1, x2) @@ -181,8 +181,8 @@ def __bool__(self: Array, /) -> bool: Performs the operation __bool__. """ # Note: This is an error here. - if self._array.shape != (): - raise TypeError("bool is only allowed on arrays with shape ()") + if self._array.ndim != 0: + raise TypeError("bool is only allowed on arrays with 0 dimensions") res = self._array.__bool__() return res @@ -216,8 +216,8 @@ def __float__(self: Array, /) -> float: Performs the operation __float__. """ # Note: This is an error here. - if self._array.shape != (): - raise TypeError("float is only allowed on arrays with shape ()") + if self._array.ndim != 0: + raise TypeError("float is only allowed on arrays with 0 dimensions") res = self._array.__float__() return res @@ -278,12 +278,12 @@ def _validate_index(key, shape): - Boolean array indices are not allowed as part of a larger tuple index. - - Integer array indices are not allowed (with the exception of shape - () arrays, which are treated the same as scalars). + - Integer array indices are not allowed (with the exception of 0-D + arrays, which are treated the same as scalars). Additionally, it should be noted that indices that would return a - scalar in NumPy will return a shape () array. Array scalars are not allowed - in the specification, only shape () arrays. This is done in the + scalar in NumPy will return a 0-D array. Array scalars are not allowed + in the specification, only 0-D arrays. This is done in the ``Array._new`` constructor, not this function. """ @@ -335,8 +335,8 @@ def _validate_index(key, shape): return key elif isinstance(key, Array): if key.dtype in _integer_dtypes: - if key.shape != (): - raise IndexError("Integer array indices with shape != () are not allowed in the array API namespace") + if key.ndim != 0: + raise IndexError("Non-zero dimensional integer array indices are not allowed in the array API namespace") return key._array elif key is Ellipsis: return key @@ -374,8 +374,8 @@ def __int__(self: Array, /) -> int: Performs the operation __int__. """ # Note: This is an error here. - if self._array.shape != (): - raise TypeError("int is only allowed on arrays with shape ()") + if self._array.ndim != 0: + raise TypeError("int is only allowed on arrays with 0 dimensions") res = self._array.__int__() return res From b58fbd24783b29d377427e837d189fa039422c9a Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 16:36:26 -0600 Subject: [PATCH 093/151] Correct disallow things like a[(0, 1), (0, 1)] in the array API namespace --- numpy/_array_api/_array_object.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 7b5531b9b76..6e451ab7aac 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -320,6 +320,8 @@ def _validate_index(key, shape): if len(key) == 1: return key raise IndexError("Boolean array indices combined with other indices are not allowed in the array API namespace") + if isinstance(idx, tuple): + raise IndexError("Nested tuple indices are not allowed in the array API namespace") if shape is None: return key From c5580134c71b68c0bd24104a6393e3cea9cb25ce Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 16:40:07 -0600 Subject: [PATCH 094/151] Add a comment about the _normalize_two_args trick --- numpy/_array_api/_array_object.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 6e451ab7aac..e523acd2d5b 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -130,6 +130,12 @@ def _normalize_two_args(x1, x2): broadcasting, so the resulting shape is the same, but this prevents NumPy from not promoting the dtype. """ + # Another option would be to use signature=(x1.dtype, x2.dtype, None), + # but that only works for ufuncs, so we would have to call the ufuncs + # directly in the operator methods. One should also note that this + # sort of trick wouldn't work for functions like searchsorted, which + # don't do normal broadcasting, but there aren't any functions like + # that in the array API namespace. if x1.ndim == 0 and x2.ndim != 0: # The _array[None] workaround was chosen because it is relatively # performant. broadcast_to(x1._array, x2.shape) is much slower. We From 6855a8ad71a5041d9a2804910f011ac09a859a48 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 16:40:44 -0600 Subject: [PATCH 095/151] Move _validate_index above the methods that are actually part of the spec --- numpy/_array_api/_array_object.py | 204 +++++++++++++++--------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index e523acd2d5b..99f0e5221b4 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -146,108 +146,6 @@ def _normalize_two_args(x1, x2): x2 = Array._new(x2._array[None]) return (x1, x2) - # Everything below this line is required by the spec. - - def __abs__(self: Array, /) -> Array: - """ - Performs the operation __abs__. - """ - res = self._array.__abs__() - return self.__class__._new(res) - - @np.errstate(all='ignore') - def __add__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __add__. - """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - self, other = self._normalize_two_args(self, other) - res = self._array.__add__(other._array) - return self.__class__._new(res) - - def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: - """ - Performs the operation __and__. - """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - self, other = self._normalize_two_args(self, other) - res = self._array.__and__(other._array) - return self.__class__._new(res) - - def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: - if api_version is not None: - raise ValueError("Unrecognized array API version") - from numpy import _array_api - return _array_api - - def __bool__(self: Array, /) -> bool: - """ - Performs the operation __bool__. - """ - # Note: This is an error here. - if self._array.ndim != 0: - raise TypeError("bool is only allowed on arrays with 0 dimensions") - res = self._array.__bool__() - return res - - def __dlpack__(self: Array, /, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule: - """ - Performs the operation __dlpack__. - """ - res = self._array.__dlpack__(stream=None) - return self.__class__._new(res) - - def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]: - """ - Performs the operation __dlpack_device__. - """ - # Note: device support is required for this - res = self._array.__dlpack_device__() - return self.__class__._new(res) - - def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: - """ - Performs the operation __eq__. - """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - self, other = self._normalize_two_args(self, other) - res = self._array.__eq__(other._array) - return self.__class__._new(res) - - def __float__(self: Array, /) -> float: - """ - Performs the operation __float__. - """ - # Note: This is an error here. - if self._array.ndim != 0: - raise TypeError("float is only allowed on arrays with 0 dimensions") - res = self._array.__float__() - return res - - @np.errstate(all='ignore') - def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __floordiv__. - """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - self, other = self._normalize_two_args(self, other) - res = self._array.__floordiv__(other._array) - return self.__class__._new(res) - - def __ge__(self: Array, other: Union[int, float, Array], /) -> Array: - """ - Performs the operation __ge__. - """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - self, other = self._normalize_two_args(self, other) - res = self._array.__ge__(other._array) - return self.__class__._new(res) - # Note: A large fraction of allowed indices are disallowed here (see the # docstring below) @staticmethod @@ -357,6 +255,108 @@ def _validate_index(key, shape): # Array() form, like a list of booleans. raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") + # Everything below this line is required by the spec. + + def __abs__(self: Array, /) -> Array: + """ + Performs the operation __abs__. + """ + res = self._array.__abs__() + return self.__class__._new(res) + + @np.errstate(all='ignore') + def __add__(self: Array, other: Union[int, float, Array], /) -> Array: + """ + Performs the operation __add__. + """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) + self, other = self._normalize_two_args(self, other) + res = self._array.__add__(other._array) + return self.__class__._new(res) + + def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: + """ + Performs the operation __and__. + """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) + self, other = self._normalize_two_args(self, other) + res = self._array.__and__(other._array) + return self.__class__._new(res) + + def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: + if api_version is not None: + raise ValueError("Unrecognized array API version") + from numpy import _array_api + return _array_api + + def __bool__(self: Array, /) -> bool: + """ + Performs the operation __bool__. + """ + # Note: This is an error here. + if self._array.ndim != 0: + raise TypeError("bool is only allowed on arrays with 0 dimensions") + res = self._array.__bool__() + return res + + def __dlpack__(self: Array, /, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule: + """ + Performs the operation __dlpack__. + """ + res = self._array.__dlpack__(stream=None) + return self.__class__._new(res) + + def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]: + """ + Performs the operation __dlpack_device__. + """ + # Note: device support is required for this + res = self._array.__dlpack_device__() + return self.__class__._new(res) + + def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: + """ + Performs the operation __eq__. + """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) + self, other = self._normalize_two_args(self, other) + res = self._array.__eq__(other._array) + return self.__class__._new(res) + + def __float__(self: Array, /) -> float: + """ + Performs the operation __float__. + """ + # Note: This is an error here. + if self._array.ndim != 0: + raise TypeError("float is only allowed on arrays with 0 dimensions") + res = self._array.__float__() + return res + + @np.errstate(all='ignore') + def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: + """ + Performs the operation __floordiv__. + """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) + self, other = self._normalize_two_args(self, other) + res = self._array.__floordiv__(other._array) + return self.__class__._new(res) + + def __ge__(self: Array, other: Union[int, float, Array], /) -> Array: + """ + Performs the operation __ge__. + """ + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) + self, other = self._normalize_two_args(self, other) + res = self._array.__ge__(other._array) + return self.__class__._new(res) + def __getitem__(self: Array, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], /) -> Array: """ Performs the operation __getitem__. From 6f98f9e0b73d4ca9e5a7d85091593ca8f0718e97 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 18:44:47 -0600 Subject: [PATCH 096/151] Remove error ignoring in the array API namespace The array API requires that elementwise functions always give a value, like nan. However, it doesn't specify that the functions need not give warnings. It is possible here to still break things by manually changing warnings into errors with np.seterr(), but this is considered unsupported behavior. --- numpy/_array_api/_array_object.py | 21 ------------------ numpy/_array_api/_elementwise_functions.py | 25 ---------------------- 2 files changed, 46 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 99f0e5221b4..0219c853236 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -264,7 +264,6 @@ def __abs__(self: Array, /) -> Array: res = self._array.__abs__() return self.__class__._new(res) - @np.errstate(all='ignore') def __add__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __add__. @@ -336,7 +335,6 @@ def __float__(self: Array, /) -> float: res = self._array.__float__() return res - @np.errstate(all='ignore') def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __floordiv__. @@ -443,7 +441,6 @@ def __matmul__(self: Array, other: Array, /) -> Array: res = self._array.__matmul__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __mod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mod__. @@ -454,7 +451,6 @@ def __mod__(self: Array, other: Union[int, float, Array], /) -> Array: res = self._array.__mod__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __mul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mul__. @@ -501,7 +497,6 @@ def __pos__(self: Array, /) -> Array: # PEP 484 requires int to be a subtype of float, but __pow__ should not # accept int. - @np.errstate(all='ignore') def __pow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __pow__. @@ -536,7 +531,6 @@ def __setitem__(self, key, value, /): res = self._array.__setitem__(key, asarray(value)._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __sub__. @@ -549,7 +543,6 @@ def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: # PEP 484 requires int to be a subtype of float, but __truediv__ should # not accept int. - @np.errstate(all='ignore') def __truediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __truediv__. @@ -572,7 +565,6 @@ def __xor__(self: Array, other: Union[int, bool, Array], /) -> Array: res = self._array.__xor__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __iadd__. @@ -582,7 +574,6 @@ def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array: self._array.__iadd__(other._array) return self - @np.errstate(all='ignore') def __radd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __radd__. @@ -612,7 +603,6 @@ def __rand__(self: Array, other: Union[int, bool, Array], /) -> Array: res = self._array.__rand__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ifloordiv__. @@ -622,7 +612,6 @@ def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: self._array.__ifloordiv__(other._array) return self - @np.errstate(all='ignore') def __rfloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rfloordiv__. @@ -683,7 +672,6 @@ def __rmatmul__(self: Array, other: Array, /) -> Array: res = self._array.__rmatmul__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __imod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imod__. @@ -693,7 +681,6 @@ def __imod__(self: Array, other: Union[int, float, Array], /) -> Array: self._array.__imod__(other._array) return self - @np.errstate(all='ignore') def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmod__. @@ -704,7 +691,6 @@ def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array: res = self._array.__rmod__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __imul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imul__. @@ -714,7 +700,6 @@ def __imul__(self: Array, other: Union[int, float, Array], /) -> Array: self._array.__imul__(other._array) return self - @np.errstate(all='ignore') def __rmul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmul__. @@ -744,7 +729,6 @@ def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array: res = self._array.__ror__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __ipow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __ipow__. @@ -756,7 +740,6 @@ def __ipow__(self: Array, other: Union[float, Array], /) -> Array: self._array.__ipow__(other._array) return self - @np.errstate(all='ignore') def __rpow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __rpow__. @@ -790,7 +773,6 @@ def __rrshift__(self: Array, other: Union[int, Array], /) -> Array: res = self._array.__rrshift__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __isub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __isub__. @@ -800,7 +782,6 @@ def __isub__(self: Array, other: Union[int, float, Array], /) -> Array: self._array.__isub__(other._array) return self - @np.errstate(all='ignore') def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rsub__. @@ -811,7 +792,6 @@ def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: res = self._array.__rsub__(other._array) return self.__class__._new(res) - @np.errstate(all='ignore') def __itruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __itruediv__. @@ -823,7 +803,6 @@ def __itruediv__(self: Array, other: Union[float, Array], /) -> Array: self._array.__itruediv__(other._array) return self - @np.errstate(all='ignore') def __rtruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __rtruediv__. diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 0d955b614a8..ade2aab0c60 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -18,7 +18,6 @@ def abs(x: Array, /) -> Array: return Array._new(np.abs(x._array)) # Note: the function name is different here -@np.errstate(all='ignore') def acos(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arccos `. @@ -30,7 +29,6 @@ def acos(x: Array, /) -> Array: return Array._new(np.arccos(x._array)) # Note: the function name is different here -@np.errstate(all='ignore') def acosh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arccosh `. @@ -41,7 +39,6 @@ def acosh(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in acosh') return Array._new(np.arccosh(x._array)) -@np.errstate(all='ignore') def add(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.add `. @@ -54,7 +51,6 @@ def add(x1: Array, x2: Array, /) -> Array: return Array._new(np.add(x1._array, x2._array)) # Note: the function name is different here -@np.errstate(all='ignore') def asin(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arcsin `. @@ -66,7 +62,6 @@ def asin(x: Array, /) -> Array: return Array._new(np.arcsin(x._array)) # Note: the function name is different here -@np.errstate(all='ignore') def asinh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arcsinh `. @@ -101,7 +96,6 @@ def atan2(x1: Array, x2: Array, /) -> Array: return Array._new(np.arctan2(x1._array, x2._array)) # Note: the function name is different here -@np.errstate(all='ignore') def atanh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.arctanh `. @@ -199,7 +193,6 @@ def ceil(x: Array, /) -> Array: return x return Array._new(np.ceil(x._array)) -@np.errstate(all='ignore') def cos(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.cos `. @@ -210,7 +203,6 @@ def cos(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in cos') return Array._new(np.cos(x._array)) -@np.errstate(all='ignore') def cosh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.cosh `. @@ -221,7 +213,6 @@ def cosh(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in cosh') return Array._new(np.cosh(x._array)) -@np.errstate(all='ignore') def divide(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.divide `. @@ -242,7 +233,6 @@ def equal(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.equal(x1._array, x2._array)) -@np.errstate(all='ignore') def exp(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.exp `. @@ -253,7 +243,6 @@ def exp(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in exp') return Array._new(np.exp(x._array)) -@np.errstate(all='ignore') def expm1(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.expm1 `. @@ -277,7 +266,6 @@ def floor(x: Array, /) -> Array: return x return Array._new(np.floor(x._array)) -@np.errstate(all='ignore') def floor_divide(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.floor_divide `. @@ -363,7 +351,6 @@ def less_equal(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.less_equal(x1._array, x2._array)) -@np.errstate(all='ignore') def log(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log `. @@ -374,7 +361,6 @@ def log(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in log') return Array._new(np.log(x._array)) -@np.errstate(all='ignore') def log1p(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log1p `. @@ -385,7 +371,6 @@ def log1p(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in log1p') return Array._new(np.log1p(x._array)) -@np.errstate(all='ignore') def log2(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log2 `. @@ -396,7 +381,6 @@ def log2(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in log2') return Array._new(np.log2(x._array)) -@np.errstate(all='ignore') def log10(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log10 `. @@ -461,7 +445,6 @@ def logical_xor(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_xor(x1._array, x2._array)) -@np.errstate(all='ignore') def multiply(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.multiply `. @@ -503,7 +486,6 @@ def positive(x: Array, /) -> Array: return Array._new(np.positive(x._array)) # Note: the function name is different here -@np.errstate(all='ignore') def pow(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.power `. @@ -515,7 +497,6 @@ def pow(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.power(x1._array, x2._array)) -@np.errstate(all='ignore') def remainder(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.remainder `. @@ -547,7 +528,6 @@ def sign(x: Array, /) -> Array: raise TypeError('Only numeric dtypes are allowed in sign') return Array._new(np.sign(x._array)) -@np.errstate(all='ignore') def sin(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sin `. @@ -558,7 +538,6 @@ def sin(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in sin') return Array._new(np.sin(x._array)) -@np.errstate(all='ignore') def sinh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sinh `. @@ -569,7 +548,6 @@ def sinh(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in sinh') return Array._new(np.sinh(x._array)) -@np.errstate(all='ignore') def square(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.square `. @@ -580,7 +558,6 @@ def square(x: Array, /) -> Array: raise TypeError('Only numeric dtypes are allowed in square') return Array._new(np.square(x._array)) -@np.errstate(all='ignore') def sqrt(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sqrt `. @@ -591,7 +568,6 @@ def sqrt(x: Array, /) -> Array: raise TypeError('Only floating-point dtypes are allowed in sqrt') return Array._new(np.sqrt(x._array)) -@np.errstate(all='ignore') def subtract(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.subtract `. @@ -603,7 +579,6 @@ def subtract(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.subtract(x1._array, x2._array)) -@np.errstate(all='ignore') def tan(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.tan `. From 48df7af2089d1f7be8aee646a89d5423b9a9a6b1 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 18:51:05 -0600 Subject: [PATCH 097/151] Fix some spelling errors --- numpy/_array_api/_array_object.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 0219c853236..547143a4b52 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -837,7 +837,7 @@ def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array: @property def dtype(self) -> Dtype: """ - Array API compatible wrapper for :py:meth:`np.ndaray.dtype `. + Array API compatible wrapper for :py:meth:`np.ndarray.dtype `. See its docstring for more information. """ @@ -856,7 +856,7 @@ def device(self) -> Device: @property def ndim(self) -> int: """ - Array API compatible wrapper for :py:meth:`np.ndaray.ndim `. + Array API compatible wrapper for :py:meth:`np.ndarray.ndim `. See its docstring for more information. """ @@ -865,7 +865,7 @@ def ndim(self) -> int: @property def shape(self) -> Tuple[int, ...]: """ - Array API compatible wrapper for :py:meth:`np.ndaray.shape `. + Array API compatible wrapper for :py:meth:`np.ndarray.shape `. See its docstring for more information. """ @@ -874,7 +874,7 @@ def shape(self) -> Tuple[int, ...]: @property def size(self) -> int: """ - Array API compatible wrapper for :py:meth:`np.ndaray.size `. + Array API compatible wrapper for :py:meth:`np.ndarray.size `. See its docstring for more information. """ @@ -883,7 +883,7 @@ def size(self) -> int: @property def T(self) -> Array: """ - Array API compatible wrapper for :py:meth:`np.ndaray.T `. + Array API compatible wrapper for :py:meth:`np.ndarray.T `. See its docstring for more information. """ From 3cab20ee117d39c74abd2a28f142529e379844b1 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 15 Jul 2021 18:51:13 -0600 Subject: [PATCH 098/151] Make numpy._array_api.Array.device return "cpu" --- numpy/_array_api/_array_object.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 547143a4b52..0659b7b0580 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -845,13 +845,7 @@ def dtype(self) -> Dtype: @property def device(self) -> Device: - """ - Array API compatible wrapper for :py:meth:`np.ndaray.device `. - - See its docstring for more information. - """ - # Note: device support is required for this - raise NotImplementedError("The device attribute is not yet implemented") + return 'cpu' @property def ndim(self) -> int: From 185d06d45cbc21ad06d7719ff265b1132d1c41ff Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 16 Jul 2021 15:01:09 -0600 Subject: [PATCH 099/151] Guard against non-array API inputs in the array API result_type() --- numpy/_array_api/_data_type_functions.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index fe5c7557f9c..7e0c35a6597 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._array_object import Array +from ._dtypes import _all_dtypes from dataclasses import dataclass from typing import TYPE_CHECKING, List, Tuple, Union @@ -93,4 +94,12 @@ def result_type(*arrays_and_dtypes: Sequence[Union[Array, Dtype]]) -> Dtype: See its docstring for more information. """ - return np.result_type(*(a._array if isinstance(a, Array) else a for a in arrays_and_dtypes)) + A = [] + for a in arrays_and_dtypes: + if isinstance(a, Array): + a = a._array + elif isinstance(a, np.ndarray) or a not in _all_dtypes: + raise TypeError("result_type() inputs must be array_api arrays or dtypes") + A.append(a) + + return np.result_type(*A) From d9b958259bda8db50949922770101217b2d0b50a Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 16 Jul 2021 15:02:10 -0600 Subject: [PATCH 100/151] Move the dtype check to the array API Array._new constructor --- numpy/_array_api/_array_object.py | 4 +++- numpy/_array_api/_creation_functions.py | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 0659b7b0580..267bd698f39 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -18,7 +18,7 @@ import operator from enum import IntEnum from ._creation_functions import asarray -from ._dtypes import _boolean_dtypes, _integer_dtypes, _floating_dtypes +from ._dtypes import _all_dtypes, _boolean_dtypes, _integer_dtypes, _floating_dtypes from typing import TYPE_CHECKING, Any, Optional, Tuple, Union if TYPE_CHECKING: @@ -61,6 +61,8 @@ def _new(cls, x, /): xa = np.empty((), x.dtype) xa[()] = x x = xa + if x.dtype not in _all_dtypes: + raise TypeError(f"The array_api namespace does not support the dtype '{x.dtype}'") obj._array = x return obj diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 2be0aea09ec..24d28a3fae4 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -32,8 +32,6 @@ def asarray(obj: Union[Array, float, NestedSequence[bool|int|float], SupportsDLP # to an object array. raise OverflowError("Integer out of bounds for array dtypes") res = np.asarray(obj, dtype=dtype) - if res.dtype not in _all_dtypes: - raise TypeError(f"The array_api namespace does not support the dtype '{res.dtype}'") return Array._new(res) def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: From 56345ffb82af39149f7cdf8720089e0ba42e798c Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 16 Jul 2021 15:02:44 -0600 Subject: [PATCH 101/151] Use asarray to convert a scalar into an array in the array API Array constructor --- numpy/_array_api/_array_object.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 267bd698f39..455e4fb63fe 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -58,9 +58,7 @@ def _new(cls, x, /): # Note: The spec does not have array scalars, only 0-D arrays. if isinstance(x, np.generic): # Convert the array scalar to a 0-D array - xa = np.empty((), x.dtype) - xa[()] = x - x = xa + x = np.asarray(x) if x.dtype not in _all_dtypes: raise TypeError(f"The array_api namespace does not support the dtype '{x.dtype}'") obj._array = x From 7c5380d61e31db60f44bdc86047c71f2b1f63458 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 16 Jul 2021 15:03:05 -0600 Subject: [PATCH 102/151] Remove an unnecessary indexing --- numpy/_array_api/_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 24d28a3fae4..e8c88d9b52b 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -97,7 +97,7 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") if isinstance(fill_value, Array) and fill_value.ndim == 0: - fill_value = fill_value._array[...] + fill_value = fill_value._array res = np.full(shape, fill_value, dtype=dtype) if res.dtype not in _all_dtypes: # This will happen if the fill value is not something that NumPy From 687e2a3b1ee167ae8dc359cf7e92b67e551dbbfe Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 16 Jul 2021 15:26:42 -0600 Subject: [PATCH 103/151] Add type hints to the array API __setitem__ --- numpy/_array_api/_array_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 455e4fb63fe..06cb1e92513 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -521,7 +521,7 @@ def __rshift__(self: Array, other: Union[int, Array], /) -> Array: res = self._array.__rshift__(other._array) return self.__class__._new(res) - def __setitem__(self, key, value, /): + def __setitem__(self, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], value: Union[int, float, bool, Array], /) -> Array: """ Performs the operation __setitem__. """ From a566cd1c7110d36d0e7a1f2746ea61e45f49eb89 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 16 Jul 2021 16:35:13 -0600 Subject: [PATCH 104/151] Change the type hint for stream __dlpack__ to just None --- numpy/_array_api/_array_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 06cb1e92513..4e3c7b344f5 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -300,11 +300,11 @@ def __bool__(self: Array, /) -> bool: res = self._array.__bool__() return res - def __dlpack__(self: Array, /, *, stream: Optional[Union[int, Any]] = None) -> PyCapsule: + def __dlpack__(self: Array, /, *, stream: None = None) -> PyCapsule: """ Performs the operation __dlpack__. """ - res = self._array.__dlpack__(stream=None) + res = self._array.__dlpack__(stream=stream) return self.__class__._new(res) def __dlpack_device__(self: Array, /) -> Tuple[IntEnum, int]: From f20be6ad3239a2e7a611ad42c9b36df7863e9883 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 19 Jul 2021 17:25:02 -0600 Subject: [PATCH 105/151] Start adding tests for the array API submodule The tests for the module will mostly focus on those things that aren't already tested by the official array API test suite (https://github.com/data-apis/array-api-tests). Currently, indexing tests are added, which test that the Array object correctly rejects otherwise valid indices that are not required by the array API spec. --- numpy/_array_api/_array_object.py | 4 +- numpy/_array_api/tests/__init__.py | 7 +++ numpy/_array_api/tests/test_array_object.py | 59 +++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 numpy/_array_api/tests/__init__.py create mode 100644 numpy/_array_api/tests/test_array_object.py diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 4e3c7b344f5..54280ef3772 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -158,7 +158,9 @@ def _validate_index(key, shape): allowed by NumPy but not required by the array API specification. We always raise ``IndexError`` on such indices (the spec does not require any specific behavior on them, but this makes the NumPy array API - namespace a minimal implementation of the spec). + namespace a minimal implementation of the spec). See + https://data-apis.org/array-api/latest/API_specification/indexing.html + for the full list of required indexing behavior This function either raises IndexError if the index ``key`` is invalid, or a new key to be used in place of ``key`` in indexing. It diff --git a/numpy/_array_api/tests/__init__.py b/numpy/_array_api/tests/__init__.py new file mode 100644 index 00000000000..536062e3827 --- /dev/null +++ b/numpy/_array_api/tests/__init__.py @@ -0,0 +1,7 @@ +""" +Tests for the array API namespace. + +Note, full compliance with the array API can be tested with the official array API test +suite https://github.com/data-apis/array-api-tests. This test suite primarily +focuses on those things that are not tested by the official test suite. +""" diff --git a/numpy/_array_api/tests/test_array_object.py b/numpy/_array_api/tests/test_array_object.py new file mode 100644 index 00000000000..49ec3b37bc4 --- /dev/null +++ b/numpy/_array_api/tests/test_array_object.py @@ -0,0 +1,59 @@ +from numpy.testing import assert_raises +import numpy as np + +from .. import ones, asarray + +def test_validate_index(): + # The indexing tests in the official array API test suite test that the + # array object correctly handles the subset of indices that are required + # by the spec. But the NumPy array API implementation specifically + # disallows any index not required by the spec, via Array._validate_index. + # This test focuses on testing that non-valid indices are correctly + # rejected. See + # https://data-apis.org/array-api/latest/API_specification/indexing.html + # and the docstring of Array._validate_index for the exact indexing + # behavior that should be allowed. This does not test indices that are + # already invalid in NumPy itself because Array will generally just pass + # such indices directly to the underlying np.ndarray. + + a = ones((3, 4)) + + # Out of bounds slices are not allowed + assert_raises(IndexError, lambda: a[:4]) + assert_raises(IndexError, lambda: a[:-4]) + assert_raises(IndexError, lambda: a[:3:-1]) + assert_raises(IndexError, lambda: a[:-5:-1]) + assert_raises(IndexError, lambda: a[3:]) + assert_raises(IndexError, lambda: a[-4:]) + assert_raises(IndexError, lambda: a[3::-1]) + assert_raises(IndexError, lambda: a[-4::-1]) + + assert_raises(IndexError, lambda: a[...,:5]) + assert_raises(IndexError, lambda: a[...,:-5]) + assert_raises(IndexError, lambda: a[...,:4:-1]) + assert_raises(IndexError, lambda: a[...,:-6:-1]) + assert_raises(IndexError, lambda: a[...,4:]) + assert_raises(IndexError, lambda: a[...,-5:]) + assert_raises(IndexError, lambda: a[...,4::-1]) + assert_raises(IndexError, lambda: a[...,-5::-1]) + + # Boolean indices cannot be part of a larger tuple index + assert_raises(IndexError, lambda: a[a[:,0]==1,0]) + assert_raises(IndexError, lambda: a[a[:,0]==1,...]) + assert_raises(IndexError, lambda: a[..., a[0]==1]) + assert_raises(IndexError, lambda: a[[True, True, True]]) + assert_raises(IndexError, lambda: a[(True, True, True),]) + + # Integer array indices are not allowed (except for 0-D) + idx = asarray([[0, 1]]) + assert_raises(IndexError, lambda: a[idx]) + assert_raises(IndexError, lambda: a[idx,]) + assert_raises(IndexError, lambda: a[[0, 1]]) + assert_raises(IndexError, lambda: a[(0, 1), (0, 1)]) + assert_raises(IndexError, lambda: a[[0, 1]]) + assert_raises(IndexError, lambda: a[np.array([[0, 1]])]) + + # np.newaxis is not allowed + assert_raises(IndexError, lambda: a[None]) + assert_raises(IndexError, lambda: a[None, ...]) + assert_raises(IndexError, lambda: a[..., None]) From e34c097981ad573c9871f5f52d1e6e5769529dda Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Tue, 20 Jul 2021 19:32:36 -0600 Subject: [PATCH 106/151] Always include the dtype in the array API Array repr --- numpy/_array_api/_array_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 54280ef3772..f8bad0b59cd 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -81,7 +81,7 @@ def __repr__(self: Array, /) -> str: """ Performs the operation __repr__. """ - return self._array.__repr__().replace('array', 'Array') + return f"Array({np.array2string(self._array, separator=', ')}, dtype={self.dtype.name})" # Helper function to match the type promotion rules in the spec def _promote_scalar(self, scalar): From f74b35996a15637cec567e0dffdacf7f3c24c7f5 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 21 Jul 2021 15:44:10 -0600 Subject: [PATCH 107/151] Add bool and int explicitly to the array API asarray() input type hints --- numpy/_array_api/_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index e8c88d9b52b..3c591ffe106 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -10,7 +10,7 @@ import numpy as np -def asarray(obj: Union[Array, float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: +def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.asarray `. From 4fd028ddf76d37e869bbde024cc1dc1456c7fb6f Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 21 Jul 2021 15:45:36 -0600 Subject: [PATCH 108/151] Implement the array API result_type() manually np.result_type() has too many behaviors that we want to avoid in the array API namespace, like value-based casting and unwanted type promotions. Instead, we implement the exact type promotion table from the spec. --- numpy/_array_api/_data_type_functions.py | 18 +++++-- numpy/_array_api/_dtypes.py | 69 ++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/_array_api/_data_type_functions.py index 7e0c35a6597..17a00cc6d07 100644 --- a/numpy/_array_api/_data_type_functions.py +++ b/numpy/_array_api/_data_type_functions.py @@ -1,7 +1,7 @@ from __future__ import annotations from ._array_object import Array -from ._dtypes import _all_dtypes +from ._dtypes import _all_dtypes, _result_type from dataclasses import dataclass from typing import TYPE_CHECKING, List, Tuple, Union @@ -94,12 +94,24 @@ def result_type(*arrays_and_dtypes: Sequence[Union[Array, Dtype]]) -> Dtype: See its docstring for more information. """ + # Note: we use a custom implementation that gives only the type promotions + # required by the spec rather than using np.result_type. NumPy implements + # too many extra type promotions like int64 + uint64 -> float64, and does + # value-based casting on scalar arrays. A = [] for a in arrays_and_dtypes: if isinstance(a, Array): - a = a._array + a = a.dtype elif isinstance(a, np.ndarray) or a not in _all_dtypes: raise TypeError("result_type() inputs must be array_api arrays or dtypes") A.append(a) - return np.result_type(*A) + if len(A) == 0: + raise ValueError("at least one array or dtype is required") + elif len(A) == 1: + return A[0] + else: + t = A[0] + for t2 in A[1:]: + t = _result_type(t, t2) + return t diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index b183c800f7a..9abe4cc8327 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -22,3 +22,72 @@ _integer_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64) _integer_or_boolean_dtypes = (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64) _numeric_dtypes = (float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64) + +_promotion_table = { + (int8, int8): int8, + (int8, int16): int16, + (int8, int32): int32, + (int8, int64): int64, + (int16, int8): int16, + (int16, int16): int16, + (int16, int32): int32, + (int16, int64): int64, + (int32, int8): int32, + (int32, int16): int32, + (int32, int32): int32, + (int32, int64): int64, + (int64, int8): int64, + (int64, int16): int64, + (int64, int32): int64, + (int64, int64): int64, + (uint8, uint8): uint8, + (uint8, uint16): uint16, + (uint8, uint32): uint32, + (uint8, uint64): uint64, + (uint16, uint8): uint16, + (uint16, uint16): uint16, + (uint16, uint32): uint32, + (uint16, uint64): uint64, + (uint32, uint8): uint32, + (uint32, uint16): uint32, + (uint32, uint32): uint32, + (uint32, uint64): uint64, + (uint64, uint8): uint64, + (uint64, uint16): uint64, + (uint64, uint32): uint64, + (uint64, uint64): uint64, + (int8, uint8): int16, + (int8, uint16): int32, + (int8, uint32): int64, + (int16, uint8): int16, + (int16, uint16): int32, + (int16, uint32): int64, + (int32, uint8): int32, + (int32, uint16): int32, + (int32, uint32): int64, + (int64, uint8): int64, + (int64, uint16): int64, + (int64, uint32): int64, + (uint8, int8): int16, + (uint16, int8): int32, + (uint32, int8): int64, + (uint8, int16): int16, + (uint16, int16): int32, + (uint32, int16): int64, + (uint8, int32): int32, + (uint16, int32): int32, + (uint32, int32): int64, + (uint8, int64): int64, + (uint16, int64): int64, + (uint32, int64): int64, + (float32, float32): float32, + (float32, float64): float64, + (float64, float32): float64, + (float64, float64): float64, + (bool, bool): bool, +} + +def _result_type(type1, type2): + if (type1, type2) in _promotion_table: + return _promotion_table[type1, type2] + raise TypeError(f"{type1} and {type2} cannot be type promoted together") From 9d5d0ec2264c86a19714cf185a5a183df14cbb94 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 21 Jul 2021 15:47:33 -0600 Subject: [PATCH 109/151] Fix a typo in an error message --- numpy/_array_api/_elementwise_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index ade2aab0c60..c07c32de735 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -113,7 +113,7 @@ def bitwise_and(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: - raise TypeError('Only integer_or_boolean dtypes are allowed in bitwise_and') + raise TypeError('Only integer or boolean dtypes are allowed in bitwise_and') x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_and(x1._array, x2._array)) From 63a9a87360ef492c46c37416b8270563e73a6349 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 21 Jul 2021 15:48:06 -0600 Subject: [PATCH 110/151] Restrict the array API namespace array operator type promotions Only those type promotions that are required by the spec are allowed. In particular, promotions across kinds, like integer + floating-point, are not allowed, except for the case of Python scalars. Tests are added for this. This commit additionally makes the operators return NotImplemented on unexpected input types rather than directly giving a TypeError. This is not strictly required by the array API spec, but it is generally considered a best practice for operator methods in Python. This same thing will be implemented for the various functions in the array API namespace in a later commit. --- numpy/_array_api/_array_object.py | 305 +++++++++++++------- numpy/_array_api/tests/test_array_object.py | 178 +++++++++++- 2 files changed, 376 insertions(+), 107 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index f8bad0b59cd..f6371fbf4d1 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -18,7 +18,8 @@ import operator from enum import IntEnum from ._creation_functions import asarray -from ._dtypes import _all_dtypes, _boolean_dtypes, _integer_dtypes, _floating_dtypes +from ._dtypes import (_all_dtypes, _boolean_dtypes, _integer_dtypes, + _integer_or_boolean_dtypes, _floating_dtypes, _numeric_dtypes) from typing import TYPE_CHECKING, Any, Optional, Tuple, Union if TYPE_CHECKING: @@ -83,6 +84,52 @@ def __repr__(self: Array, /) -> str: """ return f"Array({np.array2string(self._array, separator=', ')}, dtype={self.dtype.name})" + # These are various helper functions to make the array behavior match the + # spec in places where it either deviates from or is more strict than + # NumPy behavior + + def _check_allowed_dtypes(self, other, dtype_category, op): + """ + Helper function for operators to only allow specific input dtypes + + Use like + + other = self._check_allowed_dtypes(other, 'numeric', '__add__') + if other is NotImplemented: + return other + """ + from ._dtypes import _result_type + + _dtypes = { + 'all': _all_dtypes, + 'numeric': _numeric_dtypes, + 'integer': _integer_dtypes, + 'integer or boolean': _integer_or_boolean_dtypes, + 'boolean': _boolean_dtypes, + 'floating-point': _floating_dtypes, + } + + if self.dtype not in _dtypes[dtype_category]: + raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') + if isinstance(other, (int, float, bool)): + other = self._promote_scalar(other) + elif isinstance(other, Array): + if other.dtype not in _dtypes[dtype_category]: + raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') + else: + return NotImplemented + + res_dtype = _result_type(self.dtype, other.dtype) + if op.startswith('__i'): + # Note: NumPy will allow in-place operators in some cases where the type promoted operator does not match the left-hand side operand. For example, + + # >>> a = np.array(1, dtype=np.int8) + # >>> a += np.array(1, dtype=np.int16) + if res_dtype != self.dtype: + raise TypeError(f"Cannot perform {op} with dtypes {self.dtype} and {other.dtype}") + + return other + # Helper function to match the type promotion rules in the spec def _promote_scalar(self, scalar): """ @@ -270,8 +317,9 @@ def __add__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __add__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__add__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__add__(other._array) return self.__class__._new(res) @@ -280,8 +328,9 @@ def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __and__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__and__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__and__(other._array) return self.__class__._new(res) @@ -321,6 +370,11 @@ def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: """ Performs the operation __eq__. """ + # Even though "all" dtypes are allowed, we still require them to be + # promotable with each other. + other = self._check_allowed_dtypes(other, 'all', '__eq__') + if other is NotImplemented: + return other if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) self, other = self._normalize_two_args(self, other) @@ -341,8 +395,9 @@ def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __floordiv__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__floordiv__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__floordiv__(other._array) return self.__class__._new(res) @@ -351,8 +406,9 @@ def __ge__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ge__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__ge__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__ge__(other._array) return self.__class__._new(res) @@ -371,8 +427,9 @@ def __gt__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __gt__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__gt__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__gt__(other._array) return self.__class__._new(res) @@ -391,6 +448,8 @@ def __invert__(self: Array, /) -> Array: """ Performs the operation __invert__. """ + if self.dtype not in _integer_or_boolean_dtypes: + raise TypeError('Only integer or boolean dtypes are allowed in __invert__') res = self._array.__invert__() return self.__class__._new(res) @@ -398,8 +457,9 @@ def __le__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __le__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__le__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__le__(other._array) return self.__class__._new(res) @@ -416,8 +476,9 @@ def __lshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __lshift__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer', '__lshift__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__lshift__(other._array) return self.__class__._new(res) @@ -426,8 +487,9 @@ def __lt__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __lt__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__lt__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__lt__(other._array) return self.__class__._new(res) @@ -436,10 +498,11 @@ def __matmul__(self: Array, other: Array, /) -> Array: """ Performs the operation __matmul__. """ - if isinstance(other, (int, float, bool)): - # matmul is not defined for scalars, but without this, we may get - # the wrong error message from asarray. - other = self._promote_scalar(other) + # matmul is not defined for scalars, but without this, we may get + # the wrong error message from asarray. + other = self._check_allowed_dtypes(other, 'numeric', '__matmul__') + if other is NotImplemented: + return other res = self._array.__matmul__(other._array) return self.__class__._new(res) @@ -447,8 +510,9 @@ def __mod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mod__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__mod__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__mod__(other._array) return self.__class__._new(res) @@ -457,8 +521,9 @@ def __mul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mul__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__mul__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__mul__(other._array) return self.__class__._new(res) @@ -467,6 +532,9 @@ def __ne__(self: Array, other: Union[int, float, bool, Array], /) -> Array: """ Performs the operation __ne__. """ + other = self._check_allowed_dtypes(other, 'all', '__ne__') + if other is NotImplemented: + return other if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) self, other = self._normalize_two_args(self, other) @@ -477,6 +545,8 @@ def __neg__(self: Array, /) -> Array: """ Performs the operation __neg__. """ + if self.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in __neg__') res = self._array.__neg__() return self.__class__._new(res) @@ -484,8 +554,9 @@ def __or__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __or__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__or__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__or__(other._array) return self.__class__._new(res) @@ -494,6 +565,8 @@ def __pos__(self: Array, /) -> Array: """ Performs the operation __pos__. """ + if self.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in __pos__') res = self._array.__pos__() return self.__class__._new(res) @@ -505,10 +578,9 @@ def __pow__(self: Array, other: Union[float, Array], /) -> Array: """ from ._elementwise_functions import pow - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in __pow__') + other = self._check_allowed_dtypes(other, 'floating-point', '__pow__') + if other is NotImplemented: + return other # Note: NumPy's __pow__ does not follow type promotion rules for 0-d # arrays, so we use pow() here instead. return pow(self, other) @@ -517,8 +589,9 @@ def __rshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rshift__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer', '__rshift__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rshift__(other._array) return self.__class__._new(res) @@ -537,8 +610,9 @@ def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __sub__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__sub__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__sub__(other._array) return self.__class__._new(res) @@ -549,10 +623,9 @@ def __truediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __truediv__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in __truediv__') + other = self._check_allowed_dtypes(other, 'floating-point', '__truediv__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__truediv__(other._array) return self.__class__._new(res) @@ -561,8 +634,9 @@ def __xor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __xor__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__xor__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__xor__(other._array) return self.__class__._new(res) @@ -571,8 +645,9 @@ def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __iadd__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__iadd__') + if other is NotImplemented: + return other self._array.__iadd__(other._array) return self @@ -580,8 +655,9 @@ def __radd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __radd__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__radd__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__radd__(other._array) return self.__class__._new(res) @@ -590,8 +666,9 @@ def __iand__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __iand__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__iand__') + if other is NotImplemented: + return other self._array.__iand__(other._array) return self @@ -599,8 +676,9 @@ def __rand__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __rand__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__rand__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rand__(other._array) return self.__class__._new(res) @@ -609,8 +687,9 @@ def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ifloordiv__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__ifloordiv__') + if other is NotImplemented: + return other self._array.__ifloordiv__(other._array) return self @@ -618,8 +697,9 @@ def __rfloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rfloordiv__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__rfloordiv__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rfloordiv__(other._array) return self.__class__._new(res) @@ -628,8 +708,9 @@ def __ilshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __ilshift__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer', '__ilshift__') + if other is NotImplemented: + return other self._array.__ilshift__(other._array) return self @@ -637,8 +718,9 @@ def __rlshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rlshift__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer', '__rlshift__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rlshift__(other._array) return self.__class__._new(res) @@ -649,15 +731,17 @@ def __imatmul__(self: Array, other: Array, /) -> Array: """ # Note: NumPy does not implement __imatmul__. - if isinstance(other, (int, float, bool)): - # matmul is not defined for scalars, but without this, we may get - # the wrong error message from asarray. - other = self._promote_scalar(other) + # matmul is not defined for scalars, but without this, we may get + # the wrong error message from asarray. + other = self._check_allowed_dtypes(other, 'numeric', '__imatmul__') + if other is NotImplemented: + return other + # __imatmul__ can only be allowed when it would not change the shape # of self. other_shape = other.shape if self.shape == () or other_shape == (): - raise ValueError("@= requires at least one dimension") + raise TypeError("@= requires at least one dimension") if len(other_shape) == 1 or other_shape[-1] != other_shape[-2]: raise ValueError("@= cannot change the shape of the input array") self._array[:] = self._array.__matmul__(other._array) @@ -667,10 +751,11 @@ def __rmatmul__(self: Array, other: Array, /) -> Array: """ Performs the operation __rmatmul__. """ - if isinstance(other, (int, float, bool)): - # matmul is not defined for scalars, but without this, we may get - # the wrong error message from asarray. - other = self._promote_scalar(other) + # matmul is not defined for scalars, but without this, we may get + # the wrong error message from asarray. + other = self._check_allowed_dtypes(other, 'numeric', '__rmatmul__') + if other is NotImplemented: + return other res = self._array.__rmatmul__(other._array) return self.__class__._new(res) @@ -678,8 +763,9 @@ def __imod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imod__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__imod__') + if other is NotImplemented: + return other self._array.__imod__(other._array) return self @@ -687,8 +773,9 @@ def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmod__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__rmod__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rmod__(other._array) return self.__class__._new(res) @@ -697,8 +784,9 @@ def __imul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imul__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__imul__') + if other is NotImplemented: + return other self._array.__imul__(other._array) return self @@ -706,8 +794,9 @@ def __rmul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmul__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__rmul__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rmul__(other._array) return self.__class__._new(res) @@ -716,8 +805,9 @@ def __ior__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ior__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__ior__') + if other is NotImplemented: + return other self._array.__ior__(other._array) return self @@ -725,8 +815,9 @@ def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ror__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__ror__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__ror__(other._array) return self.__class__._new(res) @@ -735,10 +826,9 @@ def __ipow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __ipow__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in __pow__') + other = self._check_allowed_dtypes(other, 'floating-point', '__ipow__') + if other is NotImplemented: + return other self._array.__ipow__(other._array) return self @@ -748,10 +838,9 @@ def __rpow__(self: Array, other: Union[float, Array], /) -> Array: """ from ._elementwise_functions import pow - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in __pow__') + other = self._check_allowed_dtypes(other, 'floating-point', '__rpow__') + if other is NotImplemented: + return other # Note: NumPy's __pow__ does not follow the spec type promotion rules # for 0-d arrays, so we use pow() here instead. return pow(other, self) @@ -760,8 +849,9 @@ def __irshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __irshift__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer', '__irshift__') + if other is NotImplemented: + return other self._array.__irshift__(other._array) return self @@ -769,8 +859,9 @@ def __rrshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rrshift__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer', '__rrshift__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rrshift__(other._array) return self.__class__._new(res) @@ -779,8 +870,9 @@ def __isub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __isub__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__isub__') + if other is NotImplemented: + return other self._array.__isub__(other._array) return self @@ -788,8 +880,9 @@ def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rsub__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'numeric', '__rsub__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rsub__(other._array) return self.__class__._new(res) @@ -798,10 +891,9 @@ def __itruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __itruediv__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in __truediv__') + other = self._check_allowed_dtypes(other, 'floating-point', '__itruediv__') + if other is NotImplemented: + return other self._array.__itruediv__(other._array) return self @@ -809,10 +901,9 @@ def __rtruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __rtruediv__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) - if self.dtype not in _floating_dtypes or other.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in __truediv__') + other = self._check_allowed_dtypes(other, 'floating-point', '__rtruediv__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rtruediv__(other._array) return self.__class__._new(res) @@ -821,8 +912,9 @@ def __ixor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ixor__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__ixor__') + if other is NotImplemented: + return other self._array.__ixor__(other._array) return self @@ -830,8 +922,9 @@ def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __rxor__. """ - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) + other = self._check_allowed_dtypes(other, 'integer or boolean', '__rxor__') + if other is NotImplemented: + return other self, other = self._normalize_two_args(self, other) res = self._array.__rxor__(other._array) return self.__class__._new(res) diff --git a/numpy/_array_api/tests/test_array_object.py b/numpy/_array_api/tests/test_array_object.py index 49ec3b37bc4..5aba2b23c2e 100644 --- a/numpy/_array_api/tests/test_array_object.py +++ b/numpy/_array_api/tests/test_array_object.py @@ -1,7 +1,10 @@ from numpy.testing import assert_raises import numpy as np -from .. import ones, asarray +from .. import ones, asarray, result_type +from .._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, + _integer_dtypes, _integer_or_boolean_dtypes, + _numeric_dtypes, int8, int16, int32, int64, uint64) def test_validate_index(): # The indexing tests in the official array API test suite test that the @@ -57,3 +60,176 @@ def test_validate_index(): assert_raises(IndexError, lambda: a[None]) assert_raises(IndexError, lambda: a[None, ...]) assert_raises(IndexError, lambda: a[..., None]) + +def test_operators(): + # For every operator, we test that it works for the required type + # combinations and raises TypeError otherwise + binary_op_dtypes ={ + '__add__': 'numeric', + '__and__': 'integer_or_boolean', + '__eq__': 'all', + '__floordiv__': 'numeric', + '__ge__': 'numeric', + '__gt__': 'numeric', + '__le__': 'numeric', + '__lshift__': 'integer', + '__lt__': 'numeric', + '__mod__': 'numeric', + '__mul__': 'numeric', + '__ne__': 'all', + '__or__': 'integer_or_boolean', + '__pow__': 'floating', + '__rshift__': 'integer', + '__sub__': 'numeric', + '__truediv__': 'floating', + '__xor__': 'integer_or_boolean', + } + + # Recompute each time because of in-place ops + def _array_vals(): + for d in _integer_dtypes: + yield asarray(1, dtype=d) + for d in _boolean_dtypes: + yield asarray(False, dtype=d) + for d in _floating_dtypes: + yield asarray(1., dtype=d) + + for op, dtypes in binary_op_dtypes.items(): + ops = [op] + if op not in ['__eq__', '__ne__', '__le__', '__ge__', '__lt__', '__gt__']: + rop = '__r' + op[2:] + iop = '__i' + op[2:] + ops += [rop, iop] + for s in [1, 1., False]: + for _op in ops: + for a in _array_vals(): + # Test array op scalar. From the spec, the following combinations + # are supported: + + # - Python bool for a bool array dtype, + # - a Python int within the bounds of the given dtype for integer array dtypes, + # - a Python int or float for floating-point array dtypes + + # We do not do bounds checking for int scalars, but rather use the default + # NumPy behavior for casting in that case. + + if ((dtypes == "all" + or dtypes == "numeric" and a.dtype in _numeric_dtypes + or dtypes == "integer" and a.dtype in _integer_dtypes + or dtypes == "integer_or_boolean" and a.dtype in _integer_or_boolean_dtypes + or dtypes == "boolean" and a.dtype in _boolean_dtypes + or dtypes == "floating" and a.dtype in _floating_dtypes + ) + # bool is a subtype of int, which is why we avoid + # isinstance here. + and (a.dtype in _boolean_dtypes and type(s) == bool + or a.dtype in _integer_dtypes and type(s) == int + or a.dtype in _floating_dtypes and type(s) in [float, int] + )): + # Only test for no error + getattr(a, _op)(s) + else: + assert_raises(TypeError, lambda: getattr(a, _op)(s)) + + # Test array op array. + for _op in ops: + for x in _array_vals(): + for y in _array_vals(): + # See the promotion table in NEP 47 or the array + # API spec page on type promotion. Mixed kind + # promotion is not defined. + if (x.dtype == uint64 and y.dtype in [int8, int16, int32, int64] + or y.dtype == uint64 and x.dtype in [int8, int16, int32, int64] + or x.dtype in _integer_dtypes and y.dtype not in _integer_dtypes + or y.dtype in _integer_dtypes and x.dtype not in _integer_dtypes + or x.dtype in _boolean_dtypes and y.dtype not in _boolean_dtypes + or y.dtype in _boolean_dtypes and x.dtype not in _boolean_dtypes + or x.dtype in _floating_dtypes and y.dtype not in _floating_dtypes + or y.dtype in _floating_dtypes and x.dtype not in _floating_dtypes + ): + assert_raises(TypeError, lambda: getattr(x, _op)(y)) + # Ensure in-place operators only promote to the same dtype as the left operand. + elif _op.startswith('__i') and result_type(x.dtype, y.dtype) != x.dtype: + assert_raises(TypeError, lambda: getattr(x, _op)(y)) + # Ensure only those dtypes that are required for every operator are allowed. + elif (dtypes == "all" and (x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes + or x.dtype in _numeric_dtypes and y.dtype in _numeric_dtypes) + or (dtypes == "numeric" and x.dtype in _numeric_dtypes and y.dtype in _numeric_dtypes) + or dtypes == "integer" and x.dtype in _integer_dtypes and y.dtype in _numeric_dtypes + or dtypes == "integer_or_boolean" and (x.dtype in _integer_dtypes and y.dtype in _integer_dtypes + or x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes) + or dtypes == "boolean" and x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes + or dtypes == "floating" and x.dtype in _floating_dtypes and y.dtype in _floating_dtypes + ): + getattr(x, _op)(y) + else: + assert_raises(TypeError, lambda: getattr(x, _op)(y)) + + unary_op_dtypes ={ + '__invert__': 'integer_or_boolean', + '__neg__': 'numeric', + '__pos__': 'numeric', + } + for op, dtypes in unary_op_dtypes.items(): + for a in _array_vals(): + if (dtypes == "numeric" and a.dtype in _numeric_dtypes + or dtypes == "integer_or_boolean" and a.dtype in _integer_or_boolean_dtypes + ): + # Only test for no error + getattr(a, op)() + else: + assert_raises(TypeError, lambda: getattr(a, op)()) + + # Finally, matmul() must be tested separately, because it works a bit + # different from the other operations. + def _matmul_array_vals(): + for a in _array_vals(): + yield a + for d in _all_dtypes: + yield ones((3, 4), dtype=d) + yield ones((4, 2), dtype=d) + yield ones((4, 4), dtype=d) + + # Scalars always error + for _op in ['__matmul__', '__rmatmul__', '__imatmul__']: + for s in [1, 1., False]: + for a in _matmul_array_vals(): + if (type(s) in [float, int] and a.dtype in _floating_dtypes + or type(s) == int and a.dtype in _integer_dtypes): + # Type promotion is valid, but @ is not allowed on 0-D + # inputs, so the error is a ValueError + assert_raises(ValueError, lambda: getattr(a, _op)(s)) + else: + assert_raises(TypeError, lambda: getattr(a, _op)(s)) + + for x in _matmul_array_vals(): + for y in _matmul_array_vals(): + if (x.dtype == uint64 and y.dtype in [int8, int16, int32, int64] + or y.dtype == uint64 and x.dtype in [int8, int16, int32, int64] + or x.dtype in _integer_dtypes and y.dtype not in _integer_dtypes + or y.dtype in _integer_dtypes and x.dtype not in _integer_dtypes + or x.dtype in _floating_dtypes and y.dtype not in _floating_dtypes + or y.dtype in _floating_dtypes and x.dtype not in _floating_dtypes + or x.dtype in _boolean_dtypes + or y.dtype in _boolean_dtypes + ): + assert_raises(TypeError, lambda: x.__matmul__(y)) + assert_raises(TypeError, lambda: y.__rmatmul__(x)) + assert_raises(TypeError, lambda: x.__imatmul__(y)) + elif x.shape == () or y.shape == () or x.shape[1] != y.shape[0]: + assert_raises(ValueError, lambda: x.__matmul__(y)) + assert_raises(ValueError, lambda: y.__rmatmul__(x)) + if result_type(x.dtype, y.dtype) != x.dtype: + assert_raises(TypeError, lambda: x.__imatmul__(y)) + else: + assert_raises(ValueError, lambda: x.__imatmul__(y)) + else: + x.__matmul__(y) + y.__rmatmul__(x) + if result_type(x.dtype, y.dtype) != x.dtype: + assert_raises(TypeError, lambda: x.__imatmul__(y)) + elif y.shape[0] != y.shape[1]: + # This one fails because x @ y has a different shape from x + assert_raises(ValueError, lambda: x.__imatmul__(y)) + else: + x.__imatmul__(y) From a16d76388d57f34856803dfef19bacd3a9980b60 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 16:38:25 -0600 Subject: [PATCH 111/151] Use ValueError instead of TypeError for array API @= This is consistent with @ and with NumPy. --- numpy/_array_api/_array_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index f6371fbf4d1..2d999e2f39c 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -741,7 +741,7 @@ def __imatmul__(self: Array, other: Array, /) -> Array: # of self. other_shape = other.shape if self.shape == () or other_shape == (): - raise TypeError("@= requires at least one dimension") + raise ValueError("@= requires at least one dimension") if len(other_shape) == 1 or other_shape[-1] != other_shape[-2]: raise ValueError("@= cannot change the shape of the input array") self._array[:] = self._array.__matmul__(other._array) From 776b1171aa76cc912abafb8434850bc9d37bd482 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 16:39:29 -0600 Subject: [PATCH 112/151] Add some more comments about array API type promotion stuff --- numpy/_array_api/_array_object.py | 9 ++++++++- numpy/_array_api/_dtypes.py | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 2d999e2f39c..505c27839bb 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -119,12 +119,19 @@ def _check_allowed_dtypes(self, other, dtype_category, op): else: return NotImplemented + # This will raise TypeError for type combinations that are not allowed + # to promote in the spec (even if the NumPy array operator would + # promote them). res_dtype = _result_type(self.dtype, other.dtype) if op.startswith('__i'): - # Note: NumPy will allow in-place operators in some cases where the type promoted operator does not match the left-hand side operand. For example, + # Note: NumPy will allow in-place operators in some cases where + # the type promoted operator does not match the left-hand side + # operand. For example, # >>> a = np.array(1, dtype=np.int8) # >>> a += np.array(1, dtype=np.int16) + + # The spec explicitly disallows this. if res_dtype != self.dtype: raise TypeError(f"Cannot perform {op} with dtypes {self.dtype} and {other.dtype}") diff --git a/numpy/_array_api/_dtypes.py b/numpy/_array_api/_dtypes.py index 9abe4cc8327..fcdb562dac9 100644 --- a/numpy/_array_api/_dtypes.py +++ b/numpy/_array_api/_dtypes.py @@ -23,6 +23,13 @@ _integer_or_boolean_dtypes = (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64) _numeric_dtypes = (float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64) +# Note: the spec defines a restricted type promotion table compared to NumPy. +# In particular, cross-kind promotions like integer + float or boolean + +# integer are not allowed, even for functions that accept both kinds. +# Additionally, NumPy promotes signed integer + uint64 to float64, but this +# promotion is not allowed here. To be clear, Python scalar int objects are +# allowed to promote to floating-point dtypes, but only in array operators +# (see Array._promote_scalar) method in _array_object.py. _promotion_table = { (int8, int8): int8, (int8, int16): int16, From 626567645b180179159fa1807e72b26d58ce20dd Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 16:39:54 -0600 Subject: [PATCH 113/151] Prevent unwanted type promotions everywhere in the array API namespace --- numpy/_array_api/_elementwise_functions.py | 48 ++++++++++++++++++- numpy/_array_api/_linear_algebra_functions.py | 6 ++- numpy/_array_api/_manipulation_functions.py | 5 ++ numpy/_array_api/_searching_functions.py | 3 ++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index c07c32de735..67fb7034d7d 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -2,7 +2,7 @@ from ._dtypes import (_boolean_dtypes, _floating_dtypes, _integer_dtypes, _integer_or_boolean_dtypes, - _numeric_dtypes) + _numeric_dtypes, _result_type) from ._array_object import Array import numpy as np @@ -47,6 +47,8 @@ def add(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in add') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.add(x1._array, x2._array)) @@ -92,6 +94,8 @@ def atan2(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in atan2') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.arctan2(x1._array, x2._array)) @@ -114,6 +118,8 @@ def bitwise_and(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_and') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_and(x1._array, x2._array)) @@ -126,6 +132,8 @@ def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) # Note: bitwise_left_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): @@ -151,6 +159,8 @@ def bitwise_or(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_or') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_or(x1._array, x2._array)) @@ -163,6 +173,8 @@ def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) # Note: bitwise_right_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): @@ -177,6 +189,8 @@ def bitwise_xor(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: raise TypeError('Only integer or boolean dtypes are allowed in bitwise_xor') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_xor(x1._array, x2._array)) @@ -221,6 +235,8 @@ def divide(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in divide') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.divide(x1._array, x2._array)) @@ -230,6 +246,8 @@ def equal(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.equal(x1._array, x2._array)) @@ -274,6 +292,8 @@ def floor_divide(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in floor_divide') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.floor_divide(x1._array, x2._array)) @@ -285,6 +305,8 @@ def greater(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in greater') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.greater(x1._array, x2._array)) @@ -296,6 +318,8 @@ def greater_equal(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in greater_equal') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.greater_equal(x1._array, x2._array)) @@ -337,6 +361,8 @@ def less(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in less') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.less(x1._array, x2._array)) @@ -348,6 +374,8 @@ def less_equal(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in less_equal') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.less_equal(x1._array, x2._array)) @@ -399,6 +427,8 @@ def logaddexp(x1: Array, x2: Array) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in logaddexp') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logaddexp(x1._array, x2._array)) @@ -410,6 +440,8 @@ def logical_and(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_and') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_and(x1._array, x2._array)) @@ -431,6 +463,8 @@ def logical_or(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_or') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_or(x1._array, x2._array)) @@ -442,6 +476,8 @@ def logical_xor(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: raise TypeError('Only boolean dtypes are allowed in logical_xor') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_xor(x1._array, x2._array)) @@ -453,6 +489,8 @@ def multiply(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in multiply') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.multiply(x1._array, x2._array)) @@ -472,6 +510,8 @@ def not_equal(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.not_equal(x1._array, x2._array)) @@ -494,6 +534,8 @@ def pow(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: raise TypeError('Only floating-point dtypes are allowed in pow') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.power(x1._array, x2._array)) @@ -505,6 +547,8 @@ def remainder(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in remainder') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.remainder(x1._array, x2._array)) @@ -576,6 +620,8 @@ def subtract(x1: Array, x2: Array, /) -> Array: """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in subtract') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.subtract(x1._array, x2._array)) diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/_array_api/_linear_algebra_functions.py index b4b2af1343b..f13f9c54169 100644 --- a/numpy/_array_api/_linear_algebra_functions.py +++ b/numpy/_array_api/_linear_algebra_functions.py @@ -1,7 +1,7 @@ from __future__ import annotations from ._array_object import Array -from ._dtypes import _numeric_dtypes +from ._dtypes import _numeric_dtypes, _result_type from typing import Optional, Sequence, Tuple, Union @@ -27,6 +27,8 @@ def matmul(x1: Array, x2: Array, /) -> Array: # np.matmul. if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in matmul') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) return Array._new(np.matmul(x1._array, x2._array)) @@ -36,6 +38,8 @@ def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], # np.tensordot. if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in tensordot') + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) return Array._new(np.tensordot(x1._array, x2._array, axes=axes)) diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/_array_api/_manipulation_functions.py index 6308bfc2691..fa6344beb66 100644 --- a/numpy/_array_api/_manipulation_functions.py +++ b/numpy/_array_api/_manipulation_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._array_object import Array +from ._data_type_functions import result_type from typing import List, Optional, Tuple, Union @@ -14,6 +15,8 @@ def concat(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[i See its docstring for more information. """ arrays = tuple(a._array for a in arrays) + # Call result type here just to raise on disallowed type combinations + result_type(*arrays) return Array._new(np.concatenate(arrays, axis=axis)) def expand_dims(x: Array, /, *, axis: int) -> Array: @@ -63,4 +66,6 @@ def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) -> See its docstring for more information. """ arrays = tuple(a._array for a in arrays) + # Call result type here just to raise on disallowed type combinations + result_type(*arrays) return Array._new(np.stack(arrays, axis=axis)) diff --git a/numpy/_array_api/_searching_functions.py b/numpy/_array_api/_searching_functions.py index 4764992a156..d8072085020 100644 --- a/numpy/_array_api/_searching_functions.py +++ b/numpy/_array_api/_searching_functions.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._array_object import Array +from ._dtypes import _result_type from typing import Optional, Tuple @@ -38,4 +39,6 @@ def where(condition: Array, x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ + # Call result type here just to raise on disallowed type combinations + _result_type(x1.dtype, x2.dtype) return Array._new(np.where(condition._array, x1._array, x2._array)) From 1e835f9f70a3cba6fc7a053edcbd1b1a01ee79b4 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 17:04:12 -0600 Subject: [PATCH 114/151] Remove some dead code --- numpy/_array_api/_array_object.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 505c27839bb..98e2f78f9e4 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -382,8 +382,6 @@ def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: other = self._check_allowed_dtypes(other, 'all', '__eq__') if other is NotImplemented: return other - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) self, other = self._normalize_two_args(self, other) res = self._array.__eq__(other._array) return self.__class__._new(res) @@ -542,8 +540,6 @@ def __ne__(self: Array, other: Union[int, float, bool, Array], /) -> Array: other = self._check_allowed_dtypes(other, 'all', '__ne__') if other is NotImplemented: return other - if isinstance(other, (int, float, bool)): - other = self._promote_scalar(other) self, other = self._normalize_two_args(self, other) res = self._array.__ne__(other._array) return self.__class__._new(res) From 64bb971096892c08416c5787d705a29bcd5b64b5 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 17:04:24 -0600 Subject: [PATCH 115/151] Add tests for Python scalar constructors on array API arrays --- numpy/_array_api/tests/test_array_object.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/numpy/_array_api/tests/test_array_object.py b/numpy/_array_api/tests/test_array_object.py index 5aba2b23c2e..25802f93cd4 100644 --- a/numpy/_array_api/tests/test_array_object.py +++ b/numpy/_array_api/tests/test_array_object.py @@ -233,3 +233,17 @@ def _matmul_array_vals(): assert_raises(ValueError, lambda: x.__imatmul__(y)) else: x.__imatmul__(y) + +def test_python_scalar_construtors(): + a = asarray(False) + b = asarray(0) + c = asarray(0.) + + assert bool(a) == bool(b) == bool(c) == False + assert int(a) == int(b) == int(c) == 0 + assert float(a) == float(b) == float(c) == 0. + + # bool/int/float should only be allowed on 0-D arrays. + assert_raises(TypeError, lambda: bool(asarray([False]))) + assert_raises(TypeError, lambda: int(asarray([0]))) + assert_raises(TypeError, lambda: float(asarray([0.]))) From deaf0bf6fc819c9c7b4dcffe0d4aee43bdc33bae Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 18:19:59 -0600 Subject: [PATCH 116/151] Fix the array API __abs__() to restrict to numeric dtypes --- numpy/_array_api/_array_object.py | 2 ++ numpy/_array_api/tests/test_array_object.py | 1 + 2 files changed, 3 insertions(+) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 98e2f78f9e4..cd16f49ee9b 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -317,6 +317,8 @@ def __abs__(self: Array, /) -> Array: """ Performs the operation __abs__. """ + if self.dtype not in _numeric_dtypes: + raise TypeError('Only numeric dtypes are allowed in __abs__') res = self._array.__abs__() return self.__class__._new(res) diff --git a/numpy/_array_api/tests/test_array_object.py b/numpy/_array_api/tests/test_array_object.py index 25802f93cd4..22078bbee7b 100644 --- a/numpy/_array_api/tests/test_array_object.py +++ b/numpy/_array_api/tests/test_array_object.py @@ -166,6 +166,7 @@ def _array_vals(): assert_raises(TypeError, lambda: getattr(x, _op)(y)) unary_op_dtypes ={ + '__abs__': 'numeric', '__invert__': 'integer_or_boolean', '__neg__': 'numeric', '__pos__': 'numeric', From e7f6dfecccc9dc84520af1a9f0000b3b0d0f4895 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 22 Jul 2021 18:20:44 -0600 Subject: [PATCH 117/151] Fix the array API trunc() to return the same dtype as the input It is similar to floor() and ceil() but I missed it previously. --- numpy/_array_api/_elementwise_functions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/_array_api/_elementwise_functions.py index 67fb7034d7d..7833ebe54d5 100644 --- a/numpy/_array_api/_elementwise_functions.py +++ b/numpy/_array_api/_elementwise_functions.py @@ -653,4 +653,7 @@ def trunc(x: Array, /) -> Array: """ if x.dtype not in _numeric_dtypes: raise TypeError('Only numeric dtypes are allowed in trunc') + if x.dtype in _integer_dtypes: + # Note: The return dtype of trunc is the same as the input + return x return Array._new(np.trunc(x._array)) From e4b7205fbaece2b604b0ac2b11a586a9f7c6b3dd Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 14:17:09 -0600 Subject: [PATCH 118/151] Fix the array API Array.__setitem__ --- numpy/_array_api/_array_object.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index cd16f49ee9b..13b093f4f61 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -608,8 +608,7 @@ def __setitem__(self, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, e # Note: Only indices required by the spec are allowed. See the # docstring of _validate_index key = self._validate_index(key, self.shape) - res = self._array.__setitem__(key, asarray(value)._array) - return self.__class__._new(res) + self._array.__setitem__(key, asarray(value)._array) def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: """ From 65ed981e94b166f2fb87f1239308f4b01897e617 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 15:43:41 -0600 Subject: [PATCH 119/151] Add tests for error cases for the array API elementwise functions --- .../tests/test_elementwise_functions.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 numpy/_array_api/tests/test_elementwise_functions.py diff --git a/numpy/_array_api/tests/test_elementwise_functions.py b/numpy/_array_api/tests/test_elementwise_functions.py new file mode 100644 index 00000000000..994cb0bf0b3 --- /dev/null +++ b/numpy/_array_api/tests/test_elementwise_functions.py @@ -0,0 +1,110 @@ +from inspect import getfullargspec + +from numpy.testing import assert_raises + +from .. import asarray, _elementwise_functions +from .._elementwise_functions import bitwise_left_shift, bitwise_right_shift +from .._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, + _integer_dtypes, _integer_or_boolean_dtypes, + _numeric_dtypes) + +def nargs(func): + return len(getfullargspec(func).args) + +def test_function_types(): + # Test that every function accepts only the required input types. We only + # test the negative cases here (error). The positive cases are tested in + # the array API test suite. + + elementwise_function_input_types = { + 'abs': 'numeric', + 'acos': 'floating', + 'acosh': 'floating', + 'add': 'numeric', + 'asin': 'floating', + 'asinh': 'floating', + 'atan': 'floating', + 'atan2': 'floating', + 'atanh': 'floating', + 'bitwise_and': 'integer_or_boolean', + 'bitwise_invert': 'integer_or_boolean', + 'bitwise_left_shift': 'integer', + 'bitwise_or': 'integer_or_boolean', + 'bitwise_right_shift': 'integer', + 'bitwise_xor': 'integer_or_boolean', + 'ceil': 'numeric', + 'cos': 'floating', + 'cosh': 'floating', + 'divide': 'floating', + 'equal': 'all', + 'exp': 'floating', + 'expm1': 'floating', + 'floor': 'numeric', + 'floor_divide': 'numeric', + 'greater': 'numeric', + 'greater_equal': 'numeric', + 'isfinite': 'numeric', + 'isinf': 'numeric', + 'isnan': 'numeric', + 'less': 'numeric', + 'less_equal': 'numeric', + 'log': 'floating', + 'logaddexp': 'floating', + 'log10': 'floating', + 'log1p': 'floating', + 'log2': 'floating', + 'logical_and': 'boolean', + 'logical_not': 'boolean', + 'logical_or': 'boolean', + 'logical_xor': 'boolean', + 'multiply': 'numeric', + 'negative': 'numeric', + 'not_equal': 'all', + 'positive': 'numeric', + 'pow': 'floating', + 'remainder': 'numeric', + 'round': 'numeric', + 'sign': 'numeric', + 'sin': 'floating', + 'sinh': 'floating', + 'sqrt': 'floating', + 'square': 'numeric', + 'subtract': 'numeric', + 'tan': 'floating', + 'tanh': 'floating', + 'trunc': 'numeric', + } + + _dtypes = { + 'all': _all_dtypes, + 'numeric': _numeric_dtypes, + 'integer': _integer_dtypes, + 'integer_or_boolean': _integer_or_boolean_dtypes, + 'boolean': _boolean_dtypes, + 'floating': _floating_dtypes, + } + + def _array_vals(): + for d in _integer_dtypes: + yield asarray(1, dtype=d) + for d in _boolean_dtypes: + yield asarray(False, dtype=d) + for d in _floating_dtypes: + yield asarray(1., dtype=d) + + for x in _array_vals(): + for func_name, types in elementwise_function_input_types.items(): + dtypes = _dtypes[types] + func = getattr(_elementwise_functions, func_name) + if nargs(func) == 2: + for y in _array_vals(): + if x.dtype not in dtypes or y.dtype not in dtypes: + assert_raises(TypeError, lambda: func(x, y)) + else: + if x.dtype not in dtypes: + assert_raises(TypeError, lambda: func(x)) + +def test_bitwise_shift_error(): + # bitwise shift functions should raise when the second argument is negative + assert_raises(ValueError, lambda: bitwise_left_shift(asarray([1, 1]), asarray([1, -1]))) + assert_raises(ValueError, lambda: bitwise_right_shift(asarray([1, 1]), asarray([1, -1]))) From 5882962a6b7bc684c86a37b010403ee1908d57bd Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 15:45:45 -0600 Subject: [PATCH 120/151] Assume the current array API version is 2021. See https://github.com/numpy/numpy/pull/18585#discussion_r675849149. --- numpy/_array_api/_array_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_array_api/_array_object.py b/numpy/_array_api/_array_object.py index 13b093f4f61..3ff845dd77e 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/_array_api/_array_object.py @@ -345,8 +345,8 @@ def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: return self.__class__._new(res) def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: - if api_version is not None: - raise ValueError("Unrecognized array API version") + if api_version is not None and not api_version.startswith('2021.'): + raise ValueError(f"Unrecognized array API version: {api_version!r}") from numpy import _array_api return _array_api From 8680a12bbcb18a4974cd4fd31068e16b67868026 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 17:46:16 -0600 Subject: [PATCH 121/151] Only allow dtypes to be spelled with their names in the array API Other spellings like dtype=int or dtype='i' are not part of the spec. --- numpy/_array_api/_creation_functions.py | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 3c591ffe106..f92a93c5daf 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -10,6 +10,16 @@ import numpy as np +def _check_valid_dtype(dtype): + # Note: Only spelling dtypes as the dtype objects is supported. + + # We use this instead of "dtype in _all_dtypes" because the dtype objects + # define equality with the sorts of things we want to disallw. + for d in (None,) + _all_dtypes: + if dtype is d: + return + raise ValueError("dtype must be one of the supported dtypes") + def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.asarray `. @@ -19,6 +29,8 @@ def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], # _array_object imports in this file are inside the functions to avoid # circular imports from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -41,6 +53,8 @@ def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -53,6 +67,8 @@ def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -65,6 +81,8 @@ def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -77,6 +95,8 @@ def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, d See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -93,6 +113,8 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -112,6 +134,8 @@ def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dty See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -129,6 +153,8 @@ def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -150,6 +176,8 @@ def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, d See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -162,6 +190,8 @@ def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[De See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -174,6 +204,8 @@ def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") @@ -186,6 +218,8 @@ def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D See its docstring for more information. """ from ._array_object import Array + + _check_valid_dtype(dtype) if device is not None: # Note: Device support is not yet implemented on Array raise NotImplementedError("Device support is not yet implemented") From 1823e7ec93222ad9022e50448c2e9310bd218c66 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 18:01:12 -0600 Subject: [PATCH 122/151] Allow setting device='cpu' in the array API creation functions --- numpy/_array_api/_creation_functions.py | 60 ++++++++++--------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index f92a93c5daf..1d8c5499a79 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -31,9 +31,8 @@ def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") if copy is not None: # Note: copy is not yet implemented in np.asarray raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") @@ -55,9 +54,8 @@ def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.arange(start, stop=stop, step=step, dtype=dtype)) def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -69,9 +67,8 @@ def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.empty(shape, dtype=dtype)) def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -83,9 +80,8 @@ def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.empty_like(x._array, dtype=dtype)) def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -97,9 +93,8 @@ def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, d from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.eye(n_rows, M=n_cols, k=k, dtype=dtype)) def from_dlpack(x: object, /) -> Array: @@ -115,9 +110,8 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") if isinstance(fill_value, Array) and fill_value.ndim == 0: fill_value = fill_value._array res = np.full(shape, fill_value, dtype=dtype) @@ -136,9 +130,8 @@ def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dty from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") res = np.full_like(x._array, fill_value, dtype=dtype) if res.dtype not in _all_dtypes: # This will happen if the fill value is not something that NumPy @@ -155,9 +148,8 @@ def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) def meshgrid(*arrays: Sequence[Array], indexing: str = 'xy') -> List[Array, ...]: @@ -178,9 +170,8 @@ def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, d from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.ones(shape, dtype=dtype)) def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -192,9 +183,8 @@ def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[De from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.ones_like(x._array, dtype=dtype)) def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -206,9 +196,8 @@ def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.zeros(shape, dtype=dtype)) def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: @@ -220,7 +209,6 @@ def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D from ._array_object import Array _check_valid_dtype(dtype) - if device is not None: - # Note: Device support is not yet implemented on Array - raise NotImplementedError("Device support is not yet implemented") + if device not in ['cpu', None]: + raise ValueError(f"Unsupported device {device!r}") return Array._new(np.zeros_like(x._array, dtype=dtype)) From d93aad2bde7965d1fdb506a5379d8007b399b3f0 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 18:26:39 -0600 Subject: [PATCH 123/151] Enable asarray(copy=True) in the array API namespace --- numpy/_array_api/_creation_functions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 1d8c5499a79..0c39a587518 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -33,10 +33,12 @@ def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], _check_valid_dtype(dtype) if device not in ['cpu', None]: raise ValueError(f"Unsupported device {device!r}") - if copy is not None: - # Note: copy is not yet implemented in np.asarray - raise NotImplementedError("The copy keyword argument to asarray is not yet implemented") + if copy is False: + # Note: copy=False is not yet implemented in np.asarray + raise NotImplementedError("copy=False is not yet implemented") if isinstance(obj, Array) and (dtype is None or obj.dtype == dtype): + if copy is True: + return Array._new(np.array(obj._array, copy=True, dtype=dtype)) return obj if dtype is None and isinstance(obj, int) and (obj > 2**64 or obj < -2**63): # Give a better error message in this case. NumPy would convert this From 09a4f8c7fc961e9bf536060533a4fd26c35004d8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 18:27:24 -0600 Subject: [PATCH 124/151] Add a TODO comment --- numpy/_array_api/_creation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_array_api/_creation_functions.py b/numpy/_array_api/_creation_functions.py index 0c39a587518..acf78056a27 100644 --- a/numpy/_array_api/_creation_functions.py +++ b/numpy/_array_api/_creation_functions.py @@ -42,7 +42,7 @@ def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], return obj if dtype is None and isinstance(obj, int) and (obj > 2**64 or obj < -2**63): # Give a better error message in this case. NumPy would convert this - # to an object array. + # to an object array. TODO: This won't handle large integers in lists. raise OverflowError("Integer out of bounds for array dtypes") res = np.asarray(obj, dtype=dtype) return Array._new(res) From 3b91f476fbbecbd111f10efd0aae1df8eed5d667 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 23 Jul 2021 18:28:06 -0600 Subject: [PATCH 125/151] Add tests for the array API creation functions As with the other array API tests, the tests primarily focus on things that should error. Working behavior is tested by the official array API test suite. --- .../tests/test_creation_functions.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 numpy/_array_api/tests/test_creation_functions.py diff --git a/numpy/_array_api/tests/test_creation_functions.py b/numpy/_array_api/tests/test_creation_functions.py new file mode 100644 index 00000000000..654f1d9b310 --- /dev/null +++ b/numpy/_array_api/tests/test_creation_functions.py @@ -0,0 +1,103 @@ +from numpy.testing import assert_raises +import numpy as np + +from .. import all +from .._creation_functions import (asarray, arange, empty, empty_like, eye, from_dlpack, full, full_like, linspace, meshgrid, ones, ones_like, zeros, zeros_like) +from .._array_object import Array +from .._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, + _integer_dtypes, _integer_or_boolean_dtypes, + _numeric_dtypes, int8, int16, int32, int64, uint64) + +def test_asarray_errors(): + # Test various protections against incorrect usage + assert_raises(TypeError, lambda: Array([1])) + assert_raises(TypeError, lambda: asarray(['a'])) + assert_raises(ValueError, lambda: asarray([1.], dtype=np.float16)) + assert_raises(OverflowError, lambda: asarray(2**100)) + # Preferably this would be OverflowError + # assert_raises(OverflowError, lambda: asarray([2**100])) + assert_raises(TypeError, lambda: asarray([2**100])) + asarray([1], device='cpu') # Doesn't error + assert_raises(ValueError, lambda: asarray([1], device='gpu')) + + assert_raises(ValueError, lambda: asarray([1], dtype=int)) + assert_raises(ValueError, lambda: asarray([1], dtype='i')) + +def test_asarray_copy(): + a = asarray([1]) + b = asarray(a, copy=True) + a[0] = 0 + assert all(b[0] == 1) + assert all(a[0] == 0) + # Once copy=False is implemented, replace this with + # a = asarray([1]) + # b = asarray(a, copy=False) + # a[0] = 0 + # assert all(b[0] == 0) + assert_raises(NotImplementedError, lambda: asarray(a, copy=False)) + +def test_arange_errors(): + arange(1, device='cpu') # Doesn't error + assert_raises(ValueError, lambda: arange(1, device='gpu')) + assert_raises(ValueError, lambda: arange(1, dtype=int)) + assert_raises(ValueError, lambda: arange(1, dtype='i')) + +def test_empty_errors(): + empty((1,), device='cpu') # Doesn't error + assert_raises(ValueError, lambda: empty((1,), device='gpu')) + assert_raises(ValueError, lambda: empty((1,), dtype=int)) + assert_raises(ValueError, lambda: empty((1,), dtype='i')) + +def test_empty_like_errors(): + empty_like(asarray(1), device='cpu') # Doesn't error + assert_raises(ValueError, lambda: empty_like(asarray(1), device='gpu')) + assert_raises(ValueError, lambda: empty_like(asarray(1), dtype=int)) + assert_raises(ValueError, lambda: empty_like(asarray(1), dtype='i')) + +def test_eye_errors(): + eye(1, device='cpu') # Doesn't error + assert_raises(ValueError, lambda: eye(1, device='gpu')) + assert_raises(ValueError, lambda: eye(1, dtype=int)) + assert_raises(ValueError, lambda: eye(1, dtype='i')) + +def test_full_errors(): + full((1,), 0, device='cpu') # Doesn't error + assert_raises(ValueError, lambda: full((1,), 0, device='gpu')) + assert_raises(ValueError, lambda: full((1,), 0, dtype=int)) + assert_raises(ValueError, lambda: full((1,), 0, dtype='i')) + +def test_full_like_errors(): + full_like(asarray(1), 0, device='cpu') # Doesn't error + assert_raises(ValueError, lambda: full_like(asarray(1), 0, device='gpu')) + assert_raises(ValueError, lambda: full_like(asarray(1), 0, dtype=int)) + assert_raises(ValueError, lambda: full_like(asarray(1), 0, dtype='i')) + +def test_linspace_errors(): + linspace(0, 1, 10, device='cpu') # Doesn't error + assert_raises(ValueError, lambda: linspace(0, 1, 10, device='gpu')) + assert_raises(ValueError, lambda: linspace(0, 1, 10, dtype=float)) + assert_raises(ValueError, lambda: linspace(0, 1, 10, dtype='f')) + +def test_ones_errors(): + ones((1,), device='cpu') # Doesn't error + assert_raises(ValueError, lambda: ones((1,), device='gpu')) + assert_raises(ValueError, lambda: ones((1,), dtype=int)) + assert_raises(ValueError, lambda: ones((1,), dtype='i')) + +def test_ones_like_errors(): + ones_like(asarray(1), device='cpu') # Doesn't error + assert_raises(ValueError, lambda: ones_like(asarray(1), device='gpu')) + assert_raises(ValueError, lambda: ones_like(asarray(1), dtype=int)) + assert_raises(ValueError, lambda: ones_like(asarray(1), dtype='i')) + +def test_zeros_errors(): + zeros((1,), device='cpu') # Doesn't error + assert_raises(ValueError, lambda: zeros((1,), device='gpu')) + assert_raises(ValueError, lambda: zeros((1,), dtype=int)) + assert_raises(ValueError, lambda: zeros((1,), dtype='i')) + +def test_zeros_like_errors(): + zeros_like(asarray(1), device='cpu') # Doesn't error + assert_raises(ValueError, lambda: zeros_like(asarray(1), device='gpu')) + assert_raises(ValueError, lambda: zeros_like(asarray(1), dtype=int)) + assert_raises(ValueError, lambda: zeros_like(asarray(1), dtype='i')) From 6e57d829cb6628610e163524f203245b247a2839 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 16:47:05 -0600 Subject: [PATCH 126/151] Rename numpy._array_api to numpy.array_api Instead of the leading underscore, the experimentalness of the module will be indicated by omitting a warning on import. That we, we do not have to change the API from underscore to no underscore when the module is no longer experimental. --- numpy/{_array_api => array_api}/__init__.py | 6 +++--- numpy/{_array_api => array_api}/_array_object.py | 4 ++-- numpy/{_array_api => array_api}/_constants.py | 0 numpy/{_array_api => array_api}/_creation_functions.py | 0 numpy/{_array_api => array_api}/_data_type_functions.py | 0 numpy/{_array_api => array_api}/_dtypes.py | 0 numpy/{_array_api => array_api}/_elementwise_functions.py | 0 .../{_array_api => array_api}/_linear_algebra_functions.py | 0 numpy/{_array_api => array_api}/_manipulation_functions.py | 0 numpy/{_array_api => array_api}/_searching_functions.py | 0 numpy/{_array_api => array_api}/_set_functions.py | 0 numpy/{_array_api => array_api}/_sorting_functions.py | 0 numpy/{_array_api => array_api}/_statistical_functions.py | 0 numpy/{_array_api => array_api}/_typing.py | 0 numpy/{_array_api => array_api}/_utility_functions.py | 0 numpy/{_array_api => array_api}/tests/__init__.py | 0 numpy/{_array_api => array_api}/tests/test_array_object.py | 0 .../tests/test_creation_functions.py | 0 .../tests/test_elementwise_functions.py | 0 numpy/setup.py | 2 +- 20 files changed, 6 insertions(+), 6 deletions(-) rename numpy/{_array_api => array_api}/__init__.py (97%) rename numpy/{_array_api => array_api}/_array_object.py (99%) rename numpy/{_array_api => array_api}/_constants.py (100%) rename numpy/{_array_api => array_api}/_creation_functions.py (100%) rename numpy/{_array_api => array_api}/_data_type_functions.py (100%) rename numpy/{_array_api => array_api}/_dtypes.py (100%) rename numpy/{_array_api => array_api}/_elementwise_functions.py (100%) rename numpy/{_array_api => array_api}/_linear_algebra_functions.py (100%) rename numpy/{_array_api => array_api}/_manipulation_functions.py (100%) rename numpy/{_array_api => array_api}/_searching_functions.py (100%) rename numpy/{_array_api => array_api}/_set_functions.py (100%) rename numpy/{_array_api => array_api}/_sorting_functions.py (100%) rename numpy/{_array_api => array_api}/_statistical_functions.py (100%) rename numpy/{_array_api => array_api}/_typing.py (100%) rename numpy/{_array_api => array_api}/_utility_functions.py (100%) rename numpy/{_array_api => array_api}/tests/__init__.py (100%) rename numpy/{_array_api => array_api}/tests/test_array_object.py (100%) rename numpy/{_array_api => array_api}/tests/test_creation_functions.py (100%) rename numpy/{_array_api => array_api}/tests/test_elementwise_functions.py (100%) diff --git a/numpy/_array_api/__init__.py b/numpy/array_api/__init__.py similarity index 97% rename from numpy/_array_api/__init__.py rename to numpy/array_api/__init__.py index 57a4ff4e192..4650e3db8d2 100644 --- a/numpy/_array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -55,7 +55,7 @@ guaranteed to give a comprehensive coverage of the spec. Therefore, those reviewing this submodule should refer to the standard documents themselves. -- There is a custom array object, numpy._array_api.Array, which is returned +- There is a custom array object, numpy.array_api.Array, which is returned by all functions in this module. All functions in the array API namespace implicitly assume that they will only receive this object as input. The only way to create instances of this object is to use one of the array creation @@ -72,7 +72,7 @@ - Indexing: Only a subset of indices supported by NumPy are required by the spec. The Array object restricts indexing to only allow those types of indices that are required by the spec. See the docstring of the - numpy._array_api.Array._validate_indices helper function for more + numpy.array_api.Array._validate_indices helper function for more information. - Type promotion: Some type promotion rules are different in the spec. In @@ -94,7 +94,7 @@ objects to represent dtypes. - The wrapper functions in this module do not do any type checking for things - that would be impossible without leaving the _array_api namespace. For + that would be impossible without leaving the array_api namespace. For example, since the array API dtype objects are just the NumPy dtype objects, one could pass in a non-spec NumPy dtype into a function. diff --git a/numpy/_array_api/_array_object.py b/numpy/array_api/_array_object.py similarity index 99% rename from numpy/_array_api/_array_object.py rename to numpy/array_api/_array_object.py index 3ff845dd77e..24957fde64e 100644 --- a/numpy/_array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -347,8 +347,8 @@ def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: if api_version is not None and not api_version.startswith('2021.'): raise ValueError(f"Unrecognized array API version: {api_version!r}") - from numpy import _array_api - return _array_api + from numpy import array_api + return array_api def __bool__(self: Array, /) -> bool: """ diff --git a/numpy/_array_api/_constants.py b/numpy/array_api/_constants.py similarity index 100% rename from numpy/_array_api/_constants.py rename to numpy/array_api/_constants.py diff --git a/numpy/_array_api/_creation_functions.py b/numpy/array_api/_creation_functions.py similarity index 100% rename from numpy/_array_api/_creation_functions.py rename to numpy/array_api/_creation_functions.py diff --git a/numpy/_array_api/_data_type_functions.py b/numpy/array_api/_data_type_functions.py similarity index 100% rename from numpy/_array_api/_data_type_functions.py rename to numpy/array_api/_data_type_functions.py diff --git a/numpy/_array_api/_dtypes.py b/numpy/array_api/_dtypes.py similarity index 100% rename from numpy/_array_api/_dtypes.py rename to numpy/array_api/_dtypes.py diff --git a/numpy/_array_api/_elementwise_functions.py b/numpy/array_api/_elementwise_functions.py similarity index 100% rename from numpy/_array_api/_elementwise_functions.py rename to numpy/array_api/_elementwise_functions.py diff --git a/numpy/_array_api/_linear_algebra_functions.py b/numpy/array_api/_linear_algebra_functions.py similarity index 100% rename from numpy/_array_api/_linear_algebra_functions.py rename to numpy/array_api/_linear_algebra_functions.py diff --git a/numpy/_array_api/_manipulation_functions.py b/numpy/array_api/_manipulation_functions.py similarity index 100% rename from numpy/_array_api/_manipulation_functions.py rename to numpy/array_api/_manipulation_functions.py diff --git a/numpy/_array_api/_searching_functions.py b/numpy/array_api/_searching_functions.py similarity index 100% rename from numpy/_array_api/_searching_functions.py rename to numpy/array_api/_searching_functions.py diff --git a/numpy/_array_api/_set_functions.py b/numpy/array_api/_set_functions.py similarity index 100% rename from numpy/_array_api/_set_functions.py rename to numpy/array_api/_set_functions.py diff --git a/numpy/_array_api/_sorting_functions.py b/numpy/array_api/_sorting_functions.py similarity index 100% rename from numpy/_array_api/_sorting_functions.py rename to numpy/array_api/_sorting_functions.py diff --git a/numpy/_array_api/_statistical_functions.py b/numpy/array_api/_statistical_functions.py similarity index 100% rename from numpy/_array_api/_statistical_functions.py rename to numpy/array_api/_statistical_functions.py diff --git a/numpy/_array_api/_typing.py b/numpy/array_api/_typing.py similarity index 100% rename from numpy/_array_api/_typing.py rename to numpy/array_api/_typing.py diff --git a/numpy/_array_api/_utility_functions.py b/numpy/array_api/_utility_functions.py similarity index 100% rename from numpy/_array_api/_utility_functions.py rename to numpy/array_api/_utility_functions.py diff --git a/numpy/_array_api/tests/__init__.py b/numpy/array_api/tests/__init__.py similarity index 100% rename from numpy/_array_api/tests/__init__.py rename to numpy/array_api/tests/__init__.py diff --git a/numpy/_array_api/tests/test_array_object.py b/numpy/array_api/tests/test_array_object.py similarity index 100% rename from numpy/_array_api/tests/test_array_object.py rename to numpy/array_api/tests/test_array_object.py diff --git a/numpy/_array_api/tests/test_creation_functions.py b/numpy/array_api/tests/test_creation_functions.py similarity index 100% rename from numpy/_array_api/tests/test_creation_functions.py rename to numpy/array_api/tests/test_creation_functions.py diff --git a/numpy/_array_api/tests/test_elementwise_functions.py b/numpy/array_api/tests/test_elementwise_functions.py similarity index 100% rename from numpy/_array_api/tests/test_elementwise_functions.py rename to numpy/array_api/tests/test_elementwise_functions.py diff --git a/numpy/setup.py b/numpy/setup.py index 82c4c8d1b75..a0ca99919b3 100644 --- a/numpy/setup.py +++ b/numpy/setup.py @@ -4,7 +4,7 @@ def configuration(parent_package='',top_path=None): from numpy.distutils.misc_util import Configuration config = Configuration('numpy', parent_package, top_path) - config.add_subpackage('_array_api') + config.add_subpackage('array_api') config.add_subpackage('compat') config.add_subpackage('core') config.add_subpackage('distutils') From ee852b432371e144456e012ec316c117170a7340 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 16:55:51 -0600 Subject: [PATCH 127/151] Print a warning when importing the numpy.array_api submodule --- numpy/array_api/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/numpy/array_api/__init__.py b/numpy/array_api/__init__.py index 4650e3db8d2..1e9790a14a9 100644 --- a/numpy/array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -115,6 +115,10 @@ """ +import warnings +warnings.warn("The numpy.array_api submodule is still experimental. See NEP 47.", + stacklevel=2) + __all__ = [] from ._constants import e, inf, nan, pi From 7e6a026f4dff0ebe49913a119f2555562e4e93be Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 19:46:21 -0600 Subject: [PATCH 128/151] Remove no longer comment about the keepdims argument to argmin --- numpy/array_api/_searching_functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/numpy/array_api/_searching_functions.py b/numpy/array_api/_searching_functions.py index d8072085020..de5f43f3d30 100644 --- a/numpy/array_api/_searching_functions.py +++ b/numpy/array_api/_searching_functions.py @@ -13,7 +13,6 @@ def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) - See its docstring for more information. """ - # Note: this currently fails as np.argmax does not implement keepdims return Array._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: @@ -22,7 +21,6 @@ def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) - See its docstring for more information. """ - # Note: this currently fails as np.argmin does not implement keepdims return Array._new(np.asarray(np.argmin(x._array, axis=axis, keepdims=keepdims))) def nonzero(x: Array, /) -> Tuple[Array, ...]: From c23abdc57b2e6c0fa4f939085374c01c1c4452a9 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 19:57:46 -0600 Subject: [PATCH 129/151] Remove asarray() calls from the array API statistical functions asarray() is already called in Array._new. --- numpy/array_api/_statistical_functions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/numpy/array_api/_statistical_functions.py b/numpy/array_api/_statistical_functions.py index 61fc60c46f2..a606203bc99 100644 --- a/numpy/array_api/_statistical_functions.py +++ b/numpy/array_api/_statistical_functions.py @@ -10,21 +10,21 @@ def max(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep return Array._new(np.max(x._array, axis=axis, keepdims=keepdims)) def mean(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return Array._new(np.asarray(np.mean(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.mean(x._array, axis=axis, keepdims=keepdims)) def min(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: return Array._new(np.min(x._array, axis=axis, keepdims=keepdims)) def prod(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return Array._new(np.asarray(np.prod(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.prod(x._array, axis=axis, keepdims=keepdims)) def std(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: # Note: the keyword argument correction is different here - return Array._new(np.asarray(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims))) + return Array._new(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims)) def sum(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: - return Array._new(np.asarray(np.sum(x._array, axis=axis, keepdims=keepdims))) + return Array._new(np.sum(x._array, axis=axis, keepdims=keepdims)) def var(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: # Note: the keyword argument correction is different here - return Array._new(np.asarray(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims))) + return Array._new(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims)) From 5605d687019dc55e594d4e227747c72bebb71a3c Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 19:59:47 -0600 Subject: [PATCH 130/151] Remove unused import --- numpy/array_api/_array_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/array_api/_array_object.py b/numpy/array_api/_array_object.py index 24957fde64e..af70058e6ca 100644 --- a/numpy/array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -21,7 +21,7 @@ from ._dtypes import (_all_dtypes, _boolean_dtypes, _integer_dtypes, _integer_or_boolean_dtypes, _floating_dtypes, _numeric_dtypes) -from typing import TYPE_CHECKING, Any, Optional, Tuple, Union +from typing import TYPE_CHECKING, Optional, Tuple, Union if TYPE_CHECKING: from ._typing import PyCapsule, Device, Dtype From bc20d334b575f897157b1cf3eecda77f3e40e049 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 20:01:11 -0600 Subject: [PATCH 131/151] Move the array API dtype categories into the top level They are not an official part of the spec but are useful for various parts of the implementation. --- numpy/array_api/_array_object.py | 17 ++++------------- numpy/array_api/_dtypes.py | 10 ++++++++++ .../tests/test_elementwise_functions.py | 16 +++------------- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/numpy/array_api/_array_object.py b/numpy/array_api/_array_object.py index af70058e6ca..50906642dd6 100644 --- a/numpy/array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -98,23 +98,14 @@ def _check_allowed_dtypes(self, other, dtype_category, op): if other is NotImplemented: return other """ - from ._dtypes import _result_type - - _dtypes = { - 'all': _all_dtypes, - 'numeric': _numeric_dtypes, - 'integer': _integer_dtypes, - 'integer or boolean': _integer_or_boolean_dtypes, - 'boolean': _boolean_dtypes, - 'floating-point': _floating_dtypes, - } - - if self.dtype not in _dtypes[dtype_category]: + from ._dtypes import _result_type, _dtype_categories + + if self.dtype not in _dtype_categories[dtype_category]: raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) elif isinstance(other, Array): - if other.dtype not in _dtypes[dtype_category]: + if other.dtype not in _dtype_categories[dtype_category]: raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') else: return NotImplemented diff --git a/numpy/array_api/_dtypes.py b/numpy/array_api/_dtypes.py index fcdb562dac9..07be267da20 100644 --- a/numpy/array_api/_dtypes.py +++ b/numpy/array_api/_dtypes.py @@ -23,6 +23,16 @@ _integer_or_boolean_dtypes = (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64) _numeric_dtypes = (float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64) +_dtype_categories = { + 'all': _all_dtypes, + 'numeric': _numeric_dtypes, + 'integer': _integer_dtypes, + 'integer or boolean': _integer_or_boolean_dtypes, + 'boolean': _boolean_dtypes, + 'floating-point': _floating_dtypes, +} + + # Note: the spec defines a restricted type promotion table compared to NumPy. # In particular, cross-kind promotions like integer + float or boolean + # integer are not allowed, even for functions that accept both kinds. diff --git a/numpy/array_api/tests/test_elementwise_functions.py b/numpy/array_api/tests/test_elementwise_functions.py index 994cb0bf0b3..2a5ddbc8704 100644 --- a/numpy/array_api/tests/test_elementwise_functions.py +++ b/numpy/array_api/tests/test_elementwise_functions.py @@ -4,9 +4,8 @@ from .. import asarray, _elementwise_functions from .._elementwise_functions import bitwise_left_shift, bitwise_right_shift -from .._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, - _integer_dtypes, _integer_or_boolean_dtypes, - _numeric_dtypes) +from .._dtypes import (_dtype_categories, _boolean_dtypes, _floating_dtypes, + _integer_dtypes) def nargs(func): return len(getfullargspec(func).args) @@ -75,15 +74,6 @@ def test_function_types(): 'trunc': 'numeric', } - _dtypes = { - 'all': _all_dtypes, - 'numeric': _numeric_dtypes, - 'integer': _integer_dtypes, - 'integer_or_boolean': _integer_or_boolean_dtypes, - 'boolean': _boolean_dtypes, - 'floating': _floating_dtypes, - } - def _array_vals(): for d in _integer_dtypes: yield asarray(1, dtype=d) @@ -94,7 +84,7 @@ def _array_vals(): for x in _array_vals(): for func_name, types in elementwise_function_input_types.items(): - dtypes = _dtypes[types] + dtypes = _dtype_categories[types] func = getattr(_elementwise_functions, func_name) if nargs(func) == 2: for y in _array_vals(): From 6789a74312cda391b81ca803d38919555213a38f Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 20:05:46 -0600 Subject: [PATCH 132/151] Move some imports out of functions to the top of the file Some of the imports in the array API module have to be inside functions to avoid circular imports, but these ones did not. --- numpy/array_api/_array_object.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/numpy/array_api/_array_object.py b/numpy/array_api/_array_object.py index 50906642dd6..364b88f897e 100644 --- a/numpy/array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -19,7 +19,8 @@ from enum import IntEnum from ._creation_functions import asarray from ._dtypes import (_all_dtypes, _boolean_dtypes, _integer_dtypes, - _integer_or_boolean_dtypes, _floating_dtypes, _numeric_dtypes) + _integer_or_boolean_dtypes, _floating_dtypes, + _numeric_dtypes, _result_type, _dtype_categories) from typing import TYPE_CHECKING, Optional, Tuple, Union if TYPE_CHECKING: @@ -27,6 +28,8 @@ import numpy as np +from numpy import array_api + class Array: """ n-d array object for the array API namespace. @@ -98,7 +101,6 @@ def _check_allowed_dtypes(self, other, dtype_category, op): if other is NotImplemented: return other """ - from ._dtypes import _result_type, _dtype_categories if self.dtype not in _dtype_categories[dtype_category]: raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') @@ -338,7 +340,6 @@ def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: if api_version is not None and not api_version.startswith('2021.'): raise ValueError(f"Unrecognized array API version: {api_version!r}") - from numpy import array_api return array_api def __bool__(self: Array, /) -> bool: From 310929d12967cb0e8e6615466ff9b9f62fc899b6 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 4 Aug 2021 20:15:06 -0600 Subject: [PATCH 133/151] Fix casting for the array API concat() and stack() --- numpy/array_api/_manipulation_functions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/numpy/array_api/_manipulation_functions.py b/numpy/array_api/_manipulation_functions.py index fa6344beb66..e68dc6fcf5a 100644 --- a/numpy/array_api/_manipulation_functions.py +++ b/numpy/array_api/_manipulation_functions.py @@ -14,10 +14,11 @@ def concat(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[i See its docstring for more information. """ + # Note: Casting rules here are different from the np.concatenate default + # (no for scalars with axis=None, no cross-kind casting) + dtype = result_type(*arrays) arrays = tuple(a._array for a in arrays) - # Call result type here just to raise on disallowed type combinations - result_type(*arrays) - return Array._new(np.concatenate(arrays, axis=axis)) + return Array._new(np.concatenate(arrays, axis=axis, dtype=dtype)) def expand_dims(x: Array, /, *, axis: int) -> Array: """ @@ -65,7 +66,7 @@ def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) -> See its docstring for more information. """ - arrays = tuple(a._array for a in arrays) # Call result type here just to raise on disallowed type combinations result_type(*arrays) + arrays = tuple(a._array for a in arrays) return Array._new(np.stack(arrays, axis=axis)) From 3730fc06cd821ec0d7794a6ae058141400921dba Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 5 Aug 2021 17:31:47 -0600 Subject: [PATCH 134/151] Give a better error when numpy.array_api is imported in Python 3.7 --- numpy/array_api/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/numpy/array_api/__init__.py b/numpy/array_api/__init__.py index 1e9790a14a9..08d19744f33 100644 --- a/numpy/array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -115,6 +115,12 @@ """ +import sys +# numpy.array_api is 3.8+ because it makes extensive use of positional-only +# arguments. +if sys.version_info < (3, 8): + raise ImportError("The numpy.array_api submodule requires Python 3.8 or greater.") + import warnings warnings.warn("The numpy.array_api submodule is still experimental. See NEP 47.", stacklevel=2) From d74a7d0b3297e10194bd3899a0d17a63610e49a1 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 5 Aug 2021 20:01:51 -0600 Subject: [PATCH 135/151] Add a setup.py to the array_api submodule --- numpy/array_api/setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 numpy/array_api/setup.py diff --git a/numpy/array_api/setup.py b/numpy/array_api/setup.py new file mode 100644 index 00000000000..da2350c8f0d --- /dev/null +++ b/numpy/array_api/setup.py @@ -0,0 +1,10 @@ +def configuration(parent_package='', top_path=None): + from numpy.distutils.misc_util import Configuration + config = Configuration('array_api', parent_package, top_path) + config.add_subpackage('tests') + return config + + +if __name__ == '__main__': + from numpy.distutils.core import setup + setup(configuration=configuration) From fcdadee7815cbb72a1036c0ef144d73e916eae6d Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 16:09:23 -0600 Subject: [PATCH 136/151] Fix some dictionary key mismatches in the array API tests --- .../tests/test_elementwise_functions.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/numpy/array_api/tests/test_elementwise_functions.py b/numpy/array_api/tests/test_elementwise_functions.py index 2a5ddbc8704..ec76cb7a7a7 100644 --- a/numpy/array_api/tests/test_elementwise_functions.py +++ b/numpy/array_api/tests/test_elementwise_functions.py @@ -17,27 +17,27 @@ def test_function_types(): elementwise_function_input_types = { 'abs': 'numeric', - 'acos': 'floating', - 'acosh': 'floating', + 'acos': 'floating-point', + 'acosh': 'floating-point', 'add': 'numeric', - 'asin': 'floating', - 'asinh': 'floating', - 'atan': 'floating', - 'atan2': 'floating', - 'atanh': 'floating', - 'bitwise_and': 'integer_or_boolean', - 'bitwise_invert': 'integer_or_boolean', + 'asin': 'floating-point', + 'asinh': 'floating-point', + 'atan': 'floating-point', + 'atan2': 'floating-point', + 'atanh': 'floating-point', + 'bitwise_and': 'integer or boolean', + 'bitwise_invert': 'integer or boolean', 'bitwise_left_shift': 'integer', - 'bitwise_or': 'integer_or_boolean', + 'bitwise_or': 'integer or boolean', 'bitwise_right_shift': 'integer', - 'bitwise_xor': 'integer_or_boolean', + 'bitwise_xor': 'integer or boolean', 'ceil': 'numeric', - 'cos': 'floating', - 'cosh': 'floating', - 'divide': 'floating', + 'cos': 'floating-point', + 'cosh': 'floating-point', + 'divide': 'floating-point', 'equal': 'all', - 'exp': 'floating', - 'expm1': 'floating', + 'exp': 'floating-point', + 'expm1': 'floating-point', 'floor': 'numeric', 'floor_divide': 'numeric', 'greater': 'numeric', @@ -47,11 +47,11 @@ def test_function_types(): 'isnan': 'numeric', 'less': 'numeric', 'less_equal': 'numeric', - 'log': 'floating', - 'logaddexp': 'floating', - 'log10': 'floating', - 'log1p': 'floating', - 'log2': 'floating', + 'log': 'floating-point', + 'logaddexp': 'floating-point', + 'log10': 'floating-point', + 'log1p': 'floating-point', + 'log2': 'floating-point', 'logical_and': 'boolean', 'logical_not': 'boolean', 'logical_or': 'boolean', @@ -60,17 +60,17 @@ def test_function_types(): 'negative': 'numeric', 'not_equal': 'all', 'positive': 'numeric', - 'pow': 'floating', + 'pow': 'floating-point', 'remainder': 'numeric', 'round': 'numeric', 'sign': 'numeric', - 'sin': 'floating', - 'sinh': 'floating', - 'sqrt': 'floating', + 'sin': 'floating-point', + 'sinh': 'floating-point', + 'sqrt': 'floating-point', 'square': 'numeric', 'subtract': 'numeric', - 'tan': 'floating', - 'tanh': 'floating', + 'tan': 'floating-point', + 'tanh': 'floating-point', 'trunc': 'numeric', } From b6f71c8fc742e09d803c99fe41c06d6f2a81d4de Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 16:10:09 -0600 Subject: [PATCH 137/151] Make the array API submodule not break the test suite The warning is issued on import, which otherwise breaks pytest collection. If we manually import early and ignore the warning, any further imports of the module won't issue the warning again, due to the way Python caches imports. --- numpy/_pytesttester.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/numpy/_pytesttester.py b/numpy/_pytesttester.py index acfaa1ca54a..bfcbd4f1f60 100644 --- a/numpy/_pytesttester.py +++ b/numpy/_pytesttester.py @@ -137,13 +137,20 @@ def __call__(self, label='fast', verbose=1, extra_argv=None, # offset verbosity. The "-q" cancels a "-v". pytest_args += ["-q"] - # Filter out distutils cpu warnings (could be localized to - # distutils tests). ASV has problems with top level import, - # so fetch module for suppression here. with warnings.catch_warnings(): warnings.simplefilter("always") + # Filter out distutils cpu warnings (could be localized to + # distutils tests). ASV has problems with top level import, + # so fetch module for suppression here. from numpy.distutils import cpuinfo + # Ignore the warning from importing the array_api submodule. This + # warning is done on import, so it would break pytest collection, + # but importing it early here prevents the warning from being + # issued when it imported again. + warnings.simplefilter("ignore") + import numpy.array_api + # Filter out annoying import messages. Want these in both develop and # release mode. pytest_args += [ From 5c7074f90ee7093f231816cb356cb787e0f22802 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 16:24:34 -0600 Subject: [PATCH 138/151] Fix the tests for Python 3.7 The array_api submodule needs to be skipped entirely, as it uses non-3.7 compatible syntax. --- numpy/_pytesttester.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/numpy/_pytesttester.py b/numpy/_pytesttester.py index bfcbd4f1f60..1e24f75a7b6 100644 --- a/numpy/_pytesttester.py +++ b/numpy/_pytesttester.py @@ -144,12 +144,18 @@ def __call__(self, label='fast', verbose=1, extra_argv=None, # so fetch module for suppression here. from numpy.distutils import cpuinfo - # Ignore the warning from importing the array_api submodule. This - # warning is done on import, so it would break pytest collection, - # but importing it early here prevents the warning from being - # issued when it imported again. - warnings.simplefilter("ignore") - import numpy.array_api + if sys.version_info >= (3, 8): + # Ignore the warning from importing the array_api submodule. This + # warning is done on import, so it would break pytest collection, + # but importing it early here prevents the warning from being + # issued when it imported again. + warnings.simplefilter("ignore") + import numpy.array_api + else: + # The array_api submodule is Python 3.8+ only due to the use + # of positional-only argument syntax. We have to ignore it + # completely or the tests will fail at the collection stage. + pytest_args += ['--ignore-glob=numpy/array_api/*'] # Filter out annoying import messages. Want these in both develop and # release mode. From 2fe8643cce651fa2ada5619f85e3cc16524d4076 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 16:48:16 -0600 Subject: [PATCH 139/151] Fix the array API __len__ method --- numpy/array_api/_array_object.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/numpy/array_api/_array_object.py b/numpy/array_api/_array_object.py index 364b88f897e..00f50eadeed 100644 --- a/numpy/array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -468,8 +468,7 @@ def __len__(self, /) -> int: """ Performs the operation __len__. """ - res = self._array.__len__() - return self.__class__._new(res) + return self._array.__len__() def __lshift__(self: Array, other: Union[int, Array], /) -> Array: """ From 4063752757a97c444b8913947a0890f2c2387bca Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 16:57:10 -0600 Subject: [PATCH 140/151] Fix the array API unique() function --- numpy/array_api/_set_functions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/numpy/array_api/_set_functions.py b/numpy/array_api/_set_functions.py index f28c2ee7284..acd59f59734 100644 --- a/numpy/array_api/_set_functions.py +++ b/numpy/array_api/_set_functions.py @@ -12,4 +12,8 @@ def unique(x: Array, /, *, return_counts: bool = False, return_index: bool = Fal See its docstring for more information. """ - return Array._new(np.unique(x._array, return_counts=return_counts, return_index=return_index, return_inverse=return_inverse)) + res = np.unique(x._array, return_counts=return_counts, + return_index=return_index, return_inverse=return_inverse) + if isinstance(res, tuple): + return tuple(Array._new(i) for i in res) + return Array._new(res) From 1ae808401951bf8c4cbff97a30505f08741d811f Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 17:12:54 -0600 Subject: [PATCH 141/151] Make the axis argument to squeeze() in the array_api module positional-only See data-apis/array-api#100. --- numpy/array_api/_manipulation_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/array_api/_manipulation_functions.py b/numpy/array_api/_manipulation_functions.py index e68dc6fcf5a..33f5d5a28c3 100644 --- a/numpy/array_api/_manipulation_functions.py +++ b/numpy/array_api/_manipulation_functions.py @@ -52,7 +52,7 @@ def roll(x: Array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Unio """ return Array._new(np.roll(x._array, shift, axis=axis)) -def squeeze(x: Array, /, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: +def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array: """ Array API compatible wrapper for :py:func:`np.squeeze `. From f13f08f6c00ff5debf918dd50546b3215e39a5b8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 17:15:39 -0600 Subject: [PATCH 142/151] Fix the array API nonzero() function --- numpy/array_api/_searching_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/array_api/_searching_functions.py b/numpy/array_api/_searching_functions.py index de5f43f3d30..9dcc76b2de8 100644 --- a/numpy/array_api/_searching_functions.py +++ b/numpy/array_api/_searching_functions.py @@ -29,7 +29,7 @@ def nonzero(x: Array, /) -> Tuple[Array, ...]: See its docstring for more information. """ - return Array._new(np.nonzero(x._array)) + return tuple(Array._new(i) for i in np.nonzero(x._array)) def where(condition: Array, x1: Array, x2: Array, /) -> Array: """ From 21923a5fa71bfadf7dee0bb5b110cc2a5719eaac Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 18:07:46 -0600 Subject: [PATCH 143/151] Update the docstring of numpy.array_api --- numpy/array_api/__init__.py | 79 +++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/numpy/array_api/__init__.py b/numpy/array_api/__init__.py index 08d19744f33..4dc931732c5 100644 --- a/numpy/array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -1,7 +1,8 @@ """ A NumPy sub-namespace that conforms to the Python array API standard. -This submodule accompanies NEP 47, which proposes its inclusion in NumPy. +This submodule accompanies NEP 47, which proposes its inclusion in NumPy. It +is still considered experimental, and will issue a warning when imported. This is a proof-of-concept namespace that wraps the corresponding NumPy functions to give a conforming implementation of the Python array API standard @@ -38,35 +39,29 @@ in progress, but the existing tests pass on this module, with a few exceptions: - - Device support is not yet implemented in NumPy - (https://data-apis.github.io/array-api/latest/design_topics/device_support.html). - As a result, the `device` attribute of the array object is missing, and - array creation functions that take the `device` keyword argument will fail - with NotImplementedError. - - DLPack support (see https://github.com/data-apis/array-api/pull/106) is not included here, as it requires a full implementation in NumPy proper first. - - The linear algebra extension in the spec will be added in a future pull -request. - The test suite is not yet complete, and even the tests that exist are not - guaranteed to give a comprehensive coverage of the spec. Therefore, those - reviewing this submodule should refer to the standard documents themselves. - -- There is a custom array object, numpy.array_api.Array, which is returned - by all functions in this module. All functions in the array API namespace + guaranteed to give a comprehensive coverage of the spec. Therefore, when + reviewing and using this submodule, you should refer to the standard + documents themselves. There are some tests in numpy.array_api.tests, but + they primarily focus on things that are not tested by the official array API + test suite. + +- There is a custom array object, numpy.array_api.Array, which is returned by + all functions in this module. All functions in the array API namespace implicitly assume that they will only receive this object as input. The only way to create instances of this object is to use one of the array creation functions. It does not have a public constructor on the object itself. The - object is a small wrapper Python class around numpy.ndarray. The main - purpose of it is to restrict the namespace of the array object to only those - dtypes and only those methods that are required by the spec, as well as to - limit/change certain behavior that differs in the spec. In particular: + object is a small wrapper class around numpy.ndarray. The main purpose of it + is to restrict the namespace of the array object to only those dtypes and + only those methods that are required by the spec, as well as to limit/change + certain behavior that differs in the spec. In particular: - - The array API namespace does not have scalar objects, only 0-d arrays. - Operations in on Array that would create a scalar in NumPy create a 0-d + - The array API namespace does not have scalar objects, only 0-D arrays. + Operations on Array that would create a scalar in NumPy create a 0-D array. - Indexing: Only a subset of indices supported by NumPy are required by the @@ -76,12 +71,15 @@ information. - Type promotion: Some type promotion rules are different in the spec. In - particular, the spec does not have any value-based casting. The - Array._promote_scalar method promotes Python scalars to arrays, - disallowing cross-type promotions like int -> float64 that are not allowed - in the spec. Array._normalize_two_args works around some type promotion - quirks in NumPy, particularly, value-based casting that occurs when one - argument of an operation is a 0-d array. + particular, the spec does not have any value-based casting. The spec also + does not require cross-kind casting, like integer -> floating-point. Only + those promotions that are explicitly required by the array API + specification are allowed in this module. See NEP 47 for more info. + + - Functions do not automatically call asarray() on their input, and will not + work if the input type is not Array. The exception is array creation + functions, and Python operators on the Array object, which accept Python + scalars of the same type as the array dtype. - All functions include type annotations, corresponding to those given in the spec (see _typing.py for definitions of some custom types). These do not @@ -93,26 +91,31 @@ equality, but it was considered too much extra complexity to create custom objects to represent dtypes. -- The wrapper functions in this module do not do any type checking for things - that would be impossible without leaving the array_api namespace. For - example, since the array API dtype objects are just the NumPy dtype objects, - one could pass in a non-spec NumPy dtype into a function. - - All places where the implementations in this submodule are known to deviate - from their corresponding functions in NumPy are marked with "# Note" - comments. Reviewers should make note of these comments. + from their corresponding functions in NumPy are marked with "# Note:" + comments. Still TODO in this module are: -- Device support and DLPack support are not yet implemented. These require - support in NumPy itself first. +- DLPack support for numpy.ndarray is still in progress. See + https://github.com/numpy/numpy/pull/19083. -- The a non-default value for the `copy` keyword argument is not yet - implemented on asarray. This requires support in numpy.asarray() first. +- The copy=False keyword argument to asarray() is not yet implemented. This + requires support in numpy.asarray() first. - Some functions are not yet fully tested in the array API test suite, and may require updates that are not yet known until the tests are written. +- The spec is still in an RFC phase and may still have minor updates, which + will need to be reflected here. + +- The linear algebra extension in the spec will be added in a future pull + request. + +- Complex number support in array API spec is planned but not yet finalized, + as are the fft extension and certain linear algebra functions such as eig + that require complex dtypes. + """ import sys From 8f7d00ed447174d9398af3365709222b529c1cad Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Fri, 6 Aug 2021 18:22:00 -0600 Subject: [PATCH 144/151] Run (selective) black on the array_api submodule I've omitted a few changes from black that messed up the readability of some complicated if statements that were organized logically line-by-line, and some changes that use unnecessary operator spacing. --- numpy/array_api/__init__.py | 247 ++++++++++++++++-- numpy/array_api/_array_object.py | 205 +++++++++------ numpy/array_api/_creation_functions.py | 158 +++++++++-- numpy/array_api/_data_type_functions.py | 16 +- numpy/array_api/_dtypes.py | 75 ++++-- numpy/array_api/_elementwise_functions.py | 194 +++++++++----- numpy/array_api/_linear_algebra_functions.py | 16 +- numpy/array_api/_manipulation_functions.py | 18 +- numpy/array_api/_searching_functions.py | 4 + numpy/array_api/_set_functions.py | 18 +- numpy/array_api/_sorting_functions.py | 14 +- numpy/array_api/_statistical_functions.py | 65 ++++- numpy/array_api/_typing.py | 30 ++- numpy/array_api/_utility_functions.py | 18 +- numpy/array_api/setup.py | 10 +- numpy/array_api/tests/test_array_object.py | 101 ++++--- .../tests/test_creation_functions.py | 122 ++++++--- .../tests/test_elementwise_functions.py | 133 +++++----- 18 files changed, 1054 insertions(+), 390 deletions(-) diff --git a/numpy/array_api/__init__.py b/numpy/array_api/__init__.py index 4dc931732c5..53c1f385047 100644 --- a/numpy/array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -119,36 +119,221 @@ """ import sys + # numpy.array_api is 3.8+ because it makes extensive use of positional-only # arguments. if sys.version_info < (3, 8): raise ImportError("The numpy.array_api submodule requires Python 3.8 or greater.") import warnings -warnings.warn("The numpy.array_api submodule is still experimental. See NEP 47.", - stacklevel=2) + +warnings.warn( + "The numpy.array_api submodule is still experimental. See NEP 47.", stacklevel=2 +) __all__ = [] from ._constants import e, inf, nan, pi -__all__ += ['e', 'inf', 'nan', 'pi'] - -from ._creation_functions import asarray, arange, empty, empty_like, eye, from_dlpack, full, full_like, linspace, meshgrid, ones, ones_like, zeros, zeros_like - -__all__ += ['asarray', 'arange', 'empty', 'empty_like', 'eye', 'from_dlpack', 'full', 'full_like', 'linspace', 'meshgrid', 'ones', 'ones_like', 'zeros', 'zeros_like'] - -from ._data_type_functions import broadcast_arrays, broadcast_to, can_cast, finfo, iinfo, result_type - -__all__ += ['broadcast_arrays', 'broadcast_to', 'can_cast', 'finfo', 'iinfo', 'result_type'] - -from ._dtypes import int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, bool - -__all__ += ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float32', 'float64', 'bool'] - -from ._elementwise_functions import abs, acos, acosh, add, asin, asinh, atan, atan2, atanh, bitwise_and, bitwise_left_shift, bitwise_invert, bitwise_or, bitwise_right_shift, bitwise_xor, ceil, cos, cosh, divide, equal, exp, expm1, floor, floor_divide, greater, greater_equal, isfinite, isinf, isnan, less, less_equal, log, log1p, log2, log10, logaddexp, logical_and, logical_not, logical_or, logical_xor, multiply, negative, not_equal, positive, pow, remainder, round, sign, sin, sinh, square, sqrt, subtract, tan, tanh, trunc - -__all__ += ['abs', 'acos', 'acosh', 'add', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'bitwise_and', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_or', 'bitwise_right_shift', 'bitwise_xor', 'ceil', 'cos', 'cosh', 'divide', 'equal', 'exp', 'expm1', 'floor', 'floor_divide', 'greater', 'greater_equal', 'isfinite', 'isinf', 'isnan', 'less', 'less_equal', 'log', 'log1p', 'log2', 'log10', 'logaddexp', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'multiply', 'negative', 'not_equal', 'positive', 'pow', 'remainder', 'round', 'sign', 'sin', 'sinh', 'square', 'sqrt', 'subtract', 'tan', 'tanh', 'trunc'] +__all__ += ["e", "inf", "nan", "pi"] + +from ._creation_functions import ( + asarray, + arange, + empty, + empty_like, + eye, + from_dlpack, + full, + full_like, + linspace, + meshgrid, + ones, + ones_like, + zeros, + zeros_like, +) + +__all__ += [ + "asarray", + "arange", + "empty", + "empty_like", + "eye", + "from_dlpack", + "full", + "full_like", + "linspace", + "meshgrid", + "ones", + "ones_like", + "zeros", + "zeros_like", +] + +from ._data_type_functions import ( + broadcast_arrays, + broadcast_to, + can_cast, + finfo, + iinfo, + result_type, +) + +__all__ += [ + "broadcast_arrays", + "broadcast_to", + "can_cast", + "finfo", + "iinfo", + "result_type", +] + +from ._dtypes import ( + int8, + int16, + int32, + int64, + uint8, + uint16, + uint32, + uint64, + float32, + float64, + bool, +) + +__all__ += [ + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "float32", + "float64", + "bool", +] + +from ._elementwise_functions import ( + abs, + acos, + acosh, + add, + asin, + asinh, + atan, + atan2, + atanh, + bitwise_and, + bitwise_left_shift, + bitwise_invert, + bitwise_or, + bitwise_right_shift, + bitwise_xor, + ceil, + cos, + cosh, + divide, + equal, + exp, + expm1, + floor, + floor_divide, + greater, + greater_equal, + isfinite, + isinf, + isnan, + less, + less_equal, + log, + log1p, + log2, + log10, + logaddexp, + logical_and, + logical_not, + logical_or, + logical_xor, + multiply, + negative, + not_equal, + positive, + pow, + remainder, + round, + sign, + sin, + sinh, + square, + sqrt, + subtract, + tan, + tanh, + trunc, +) + +__all__ += [ + "abs", + "acos", + "acosh", + "add", + "asin", + "asinh", + "atan", + "atan2", + "atanh", + "bitwise_and", + "bitwise_left_shift", + "bitwise_invert", + "bitwise_or", + "bitwise_right_shift", + "bitwise_xor", + "ceil", + "cos", + "cosh", + "divide", + "equal", + "exp", + "expm1", + "floor", + "floor_divide", + "greater", + "greater_equal", + "isfinite", + "isinf", + "isnan", + "less", + "less_equal", + "log", + "log1p", + "log2", + "log10", + "logaddexp", + "logical_and", + "logical_not", + "logical_or", + "logical_xor", + "multiply", + "negative", + "not_equal", + "positive", + "pow", + "remainder", + "round", + "sign", + "sin", + "sinh", + "square", + "sqrt", + "subtract", + "tan", + "tanh", + "trunc", +] # einsum is not yet implemented in the array API spec. @@ -157,28 +342,36 @@ from ._linear_algebra_functions import matmul, tensordot, transpose, vecdot -__all__ += ['matmul', 'tensordot', 'transpose', 'vecdot'] +__all__ += ["matmul", "tensordot", "transpose", "vecdot"] -from ._manipulation_functions import concat, expand_dims, flip, reshape, roll, squeeze, stack +from ._manipulation_functions import ( + concat, + expand_dims, + flip, + reshape, + roll, + squeeze, + stack, +) -__all__ += ['concat', 'expand_dims', 'flip', 'reshape', 'roll', 'squeeze', 'stack'] +__all__ += ["concat", "expand_dims", "flip", "reshape", "roll", "squeeze", "stack"] from ._searching_functions import argmax, argmin, nonzero, where -__all__ += ['argmax', 'argmin', 'nonzero', 'where'] +__all__ += ["argmax", "argmin", "nonzero", "where"] from ._set_functions import unique -__all__ += ['unique'] +__all__ += ["unique"] from ._sorting_functions import argsort, sort -__all__ += ['argsort', 'sort'] +__all__ += ["argsort", "sort"] from ._statistical_functions import max, mean, min, prod, std, sum, var -__all__ += ['max', 'mean', 'min', 'prod', 'std', 'sum', 'var'] +__all__ += ["max", "mean", "min", "prod", "std", "sum", "var"] from ._utility_functions import all, any -__all__ += ['all', 'any'] +__all__ += ["all", "any"] diff --git a/numpy/array_api/_array_object.py b/numpy/array_api/_array_object.py index 00f50eadeed..0f511a577b6 100644 --- a/numpy/array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -18,11 +18,19 @@ import operator from enum import IntEnum from ._creation_functions import asarray -from ._dtypes import (_all_dtypes, _boolean_dtypes, _integer_dtypes, - _integer_or_boolean_dtypes, _floating_dtypes, - _numeric_dtypes, _result_type, _dtype_categories) +from ._dtypes import ( + _all_dtypes, + _boolean_dtypes, + _integer_dtypes, + _integer_or_boolean_dtypes, + _floating_dtypes, + _numeric_dtypes, + _result_type, + _dtype_categories, +) from typing import TYPE_CHECKING, Optional, Tuple, Union + if TYPE_CHECKING: from ._typing import PyCapsule, Device, Dtype @@ -30,6 +38,7 @@ from numpy import array_api + class Array: """ n-d array object for the array API namespace. @@ -45,6 +54,7 @@ class Array: functions, such as asarray(). """ + # Use a custom constructor instead of __init__, as manually initializing # this class is not supported API. @classmethod @@ -64,13 +74,17 @@ def _new(cls, x, /): # Convert the array scalar to a 0-D array x = np.asarray(x) if x.dtype not in _all_dtypes: - raise TypeError(f"The array_api namespace does not support the dtype '{x.dtype}'") + raise TypeError( + f"The array_api namespace does not support the dtype '{x.dtype}'" + ) obj._array = x return obj # Prevent Array() from working def __new__(cls, *args, **kwargs): - raise TypeError("The array_api Array object should not be instantiated directly. Use an array creation function, such as asarray(), instead.") + raise TypeError( + "The array_api Array object should not be instantiated directly. Use an array creation function, such as asarray(), instead." + ) # These functions are not required by the spec, but are implemented for # the sake of usability. @@ -79,7 +93,7 @@ def __str__(self: Array, /) -> str: """ Performs the operation __str__. """ - return self._array.__str__().replace('array', 'Array') + return self._array.__str__().replace("array", "Array") def __repr__(self: Array, /) -> str: """ @@ -103,12 +117,12 @@ def _check_allowed_dtypes(self, other, dtype_category, op): """ if self.dtype not in _dtype_categories[dtype_category]: - raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') + raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}") if isinstance(other, (int, float, bool)): other = self._promote_scalar(other) elif isinstance(other, Array): if other.dtype not in _dtype_categories[dtype_category]: - raise TypeError(f'Only {dtype_category} dtypes are allowed in {op}') + raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}") else: return NotImplemented @@ -116,7 +130,7 @@ def _check_allowed_dtypes(self, other, dtype_category, op): # to promote in the spec (even if the NumPy array operator would # promote them). res_dtype = _result_type(self.dtype, other.dtype) - if op.startswith('__i'): + if op.startswith("__i"): # Note: NumPy will allow in-place operators in some cases where # the type promoted operator does not match the left-hand side # operand. For example, @@ -126,7 +140,9 @@ def _check_allowed_dtypes(self, other, dtype_category, op): # The spec explicitly disallows this. if res_dtype != self.dtype: - raise TypeError(f"Cannot perform {op} with dtypes {self.dtype} and {other.dtype}") + raise TypeError( + f"Cannot perform {op} with dtypes {self.dtype} and {other.dtype}" + ) return other @@ -142,13 +158,19 @@ def _promote_scalar(self, scalar): """ if isinstance(scalar, bool): if self.dtype not in _boolean_dtypes: - raise TypeError("Python bool scalars can only be promoted with bool arrays") + raise TypeError( + "Python bool scalars can only be promoted with bool arrays" + ) elif isinstance(scalar, int): if self.dtype in _boolean_dtypes: - raise TypeError("Python int scalars cannot be promoted with bool arrays") + raise TypeError( + "Python int scalars cannot be promoted with bool arrays" + ) elif isinstance(scalar, float): if self.dtype not in _floating_dtypes: - raise TypeError("Python float scalars can only be promoted with floating-point arrays.") + raise TypeError( + "Python float scalars can only be promoted with floating-point arrays." + ) else: raise TypeError("'scalar' must be a Python scalar") @@ -253,7 +275,9 @@ def _validate_index(key, shape): except TypeError: return key if not (-size <= key.start <= max(0, size - 1)): - raise IndexError("Slices with out-of-bounds start are not allowed in the array API namespace") + raise IndexError( + "Slices with out-of-bounds start are not allowed in the array API namespace" + ) if key.stop is not None: try: operator.index(key.stop) @@ -269,12 +293,20 @@ def _validate_index(key, shape): key = tuple(Array._validate_index(idx, None) for idx in key) for idx in key: - if isinstance(idx, np.ndarray) and idx.dtype in _boolean_dtypes or isinstance(idx, (bool, np.bool_)): + if ( + isinstance(idx, np.ndarray) + and idx.dtype in _boolean_dtypes + or isinstance(idx, (bool, np.bool_)) + ): if len(key) == 1: return key - raise IndexError("Boolean array indices combined with other indices are not allowed in the array API namespace") + raise IndexError( + "Boolean array indices combined with other indices are not allowed in the array API namespace" + ) if isinstance(idx, tuple): - raise IndexError("Nested tuple indices are not allowed in the array API namespace") + raise IndexError( + "Nested tuple indices are not allowed in the array API namespace" + ) if shape is None: return key @@ -283,7 +315,9 @@ def _validate_index(key, shape): return key ellipsis_i = key.index(...) if n_ellipsis else len(key) - for idx, size in list(zip(key[:ellipsis_i], shape)) + list(zip(key[:ellipsis_i:-1], shape[:ellipsis_i:-1])): + for idx, size in list(zip(key[:ellipsis_i], shape)) + list( + zip(key[:ellipsis_i:-1], shape[:ellipsis_i:-1]) + ): Array._validate_index(idx, (size,)) return key elif isinstance(key, bool): @@ -291,18 +325,24 @@ def _validate_index(key, shape): elif isinstance(key, Array): if key.dtype in _integer_dtypes: if key.ndim != 0: - raise IndexError("Non-zero dimensional integer array indices are not allowed in the array API namespace") + raise IndexError( + "Non-zero dimensional integer array indices are not allowed in the array API namespace" + ) return key._array elif key is Ellipsis: return key elif key is None: - raise IndexError("newaxis indices are not allowed in the array API namespace") + raise IndexError( + "newaxis indices are not allowed in the array API namespace" + ) try: return operator.index(key) except TypeError: # Note: This also omits boolean arrays that are not already in # Array() form, like a list of booleans. - raise IndexError("Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace") + raise IndexError( + "Only integers, slices (`:`), ellipsis (`...`), and boolean arrays are valid indices in the array API namespace" + ) # Everything below this line is required by the spec. @@ -311,7 +351,7 @@ def __abs__(self: Array, /) -> Array: Performs the operation __abs__. """ if self.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in __abs__') + raise TypeError("Only numeric dtypes are allowed in __abs__") res = self._array.__abs__() return self.__class__._new(res) @@ -319,7 +359,7 @@ def __add__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __add__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__add__') + other = self._check_allowed_dtypes(other, "numeric", "__add__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -330,15 +370,17 @@ def __and__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __and__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__and__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__and__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) res = self._array.__and__(other._array) return self.__class__._new(res) - def __array_namespace__(self: Array, /, *, api_version: Optional[str] = None) -> object: - if api_version is not None and not api_version.startswith('2021.'): + def __array_namespace__( + self: Array, /, *, api_version: Optional[str] = None + ) -> object: + if api_version is not None and not api_version.startswith("2021."): raise ValueError(f"Unrecognized array API version: {api_version!r}") return array_api @@ -373,7 +415,7 @@ def __eq__(self: Array, other: Union[int, float, bool, Array], /) -> Array: """ # Even though "all" dtypes are allowed, we still require them to be # promotable with each other. - other = self._check_allowed_dtypes(other, 'all', '__eq__') + other = self._check_allowed_dtypes(other, "all", "__eq__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -394,7 +436,7 @@ def __floordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __floordiv__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__floordiv__') + other = self._check_allowed_dtypes(other, "numeric", "__floordiv__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -405,14 +447,20 @@ def __ge__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ge__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__ge__') + other = self._check_allowed_dtypes(other, "numeric", "__ge__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) res = self._array.__ge__(other._array) return self.__class__._new(res) - def __getitem__(self: Array, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], /) -> Array: + def __getitem__( + self: Array, + key: Union[ + int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array + ], + /, + ) -> Array: """ Performs the operation __getitem__. """ @@ -426,7 +474,7 @@ def __gt__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __gt__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__gt__') + other = self._check_allowed_dtypes(other, "numeric", "__gt__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -448,7 +496,7 @@ def __invert__(self: Array, /) -> Array: Performs the operation __invert__. """ if self.dtype not in _integer_or_boolean_dtypes: - raise TypeError('Only integer or boolean dtypes are allowed in __invert__') + raise TypeError("Only integer or boolean dtypes are allowed in __invert__") res = self._array.__invert__() return self.__class__._new(res) @@ -456,7 +504,7 @@ def __le__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __le__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__le__') + other = self._check_allowed_dtypes(other, "numeric", "__le__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -474,7 +522,7 @@ def __lshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __lshift__. """ - other = self._check_allowed_dtypes(other, 'integer', '__lshift__') + other = self._check_allowed_dtypes(other, "integer", "__lshift__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -485,7 +533,7 @@ def __lt__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __lt__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__lt__') + other = self._check_allowed_dtypes(other, "numeric", "__lt__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -498,7 +546,7 @@ def __matmul__(self: Array, other: Array, /) -> Array: """ # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. - other = self._check_allowed_dtypes(other, 'numeric', '__matmul__') + other = self._check_allowed_dtypes(other, "numeric", "__matmul__") if other is NotImplemented: return other res = self._array.__matmul__(other._array) @@ -508,7 +556,7 @@ def __mod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mod__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__mod__') + other = self._check_allowed_dtypes(other, "numeric", "__mod__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -519,7 +567,7 @@ def __mul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __mul__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__mul__') + other = self._check_allowed_dtypes(other, "numeric", "__mul__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -530,7 +578,7 @@ def __ne__(self: Array, other: Union[int, float, bool, Array], /) -> Array: """ Performs the operation __ne__. """ - other = self._check_allowed_dtypes(other, 'all', '__ne__') + other = self._check_allowed_dtypes(other, "all", "__ne__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -542,7 +590,7 @@ def __neg__(self: Array, /) -> Array: Performs the operation __neg__. """ if self.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in __neg__') + raise TypeError("Only numeric dtypes are allowed in __neg__") res = self._array.__neg__() return self.__class__._new(res) @@ -550,7 +598,7 @@ def __or__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __or__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__or__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__or__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -562,7 +610,7 @@ def __pos__(self: Array, /) -> Array: Performs the operation __pos__. """ if self.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in __pos__') + raise TypeError("Only numeric dtypes are allowed in __pos__") res = self._array.__pos__() return self.__class__._new(res) @@ -574,7 +622,7 @@ def __pow__(self: Array, other: Union[float, Array], /) -> Array: """ from ._elementwise_functions import pow - other = self._check_allowed_dtypes(other, 'floating-point', '__pow__') + other = self._check_allowed_dtypes(other, "floating-point", "__pow__") if other is NotImplemented: return other # Note: NumPy's __pow__ does not follow type promotion rules for 0-d @@ -585,14 +633,21 @@ def __rshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rshift__. """ - other = self._check_allowed_dtypes(other, 'integer', '__rshift__') + other = self._check_allowed_dtypes(other, "integer", "__rshift__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) res = self._array.__rshift__(other._array) return self.__class__._new(res) - def __setitem__(self, key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array], value: Union[int, float, bool, Array], /) -> Array: + def __setitem__( + self, + key: Union[ + int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], Array + ], + value: Union[int, float, bool, Array], + /, + ) -> Array: """ Performs the operation __setitem__. """ @@ -605,7 +660,7 @@ def __sub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __sub__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__sub__') + other = self._check_allowed_dtypes(other, "numeric", "__sub__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -618,7 +673,7 @@ def __truediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __truediv__. """ - other = self._check_allowed_dtypes(other, 'floating-point', '__truediv__') + other = self._check_allowed_dtypes(other, "floating-point", "__truediv__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -629,7 +684,7 @@ def __xor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __xor__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__xor__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__xor__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -640,7 +695,7 @@ def __iadd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __iadd__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__iadd__') + other = self._check_allowed_dtypes(other, "numeric", "__iadd__") if other is NotImplemented: return other self._array.__iadd__(other._array) @@ -650,7 +705,7 @@ def __radd__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __radd__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__radd__') + other = self._check_allowed_dtypes(other, "numeric", "__radd__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -661,7 +716,7 @@ def __iand__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __iand__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__iand__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__iand__") if other is NotImplemented: return other self._array.__iand__(other._array) @@ -671,7 +726,7 @@ def __rand__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __rand__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__rand__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__rand__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -682,7 +737,7 @@ def __ifloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __ifloordiv__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__ifloordiv__') + other = self._check_allowed_dtypes(other, "numeric", "__ifloordiv__") if other is NotImplemented: return other self._array.__ifloordiv__(other._array) @@ -692,7 +747,7 @@ def __rfloordiv__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rfloordiv__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__rfloordiv__') + other = self._check_allowed_dtypes(other, "numeric", "__rfloordiv__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -703,7 +758,7 @@ def __ilshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __ilshift__. """ - other = self._check_allowed_dtypes(other, 'integer', '__ilshift__') + other = self._check_allowed_dtypes(other, "integer", "__ilshift__") if other is NotImplemented: return other self._array.__ilshift__(other._array) @@ -713,7 +768,7 @@ def __rlshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rlshift__. """ - other = self._check_allowed_dtypes(other, 'integer', '__rlshift__') + other = self._check_allowed_dtypes(other, "integer", "__rlshift__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -728,7 +783,7 @@ def __imatmul__(self: Array, other: Array, /) -> Array: # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. - other = self._check_allowed_dtypes(other, 'numeric', '__imatmul__') + other = self._check_allowed_dtypes(other, "numeric", "__imatmul__") if other is NotImplemented: return other @@ -748,7 +803,7 @@ def __rmatmul__(self: Array, other: Array, /) -> Array: """ # matmul is not defined for scalars, but without this, we may get # the wrong error message from asarray. - other = self._check_allowed_dtypes(other, 'numeric', '__rmatmul__') + other = self._check_allowed_dtypes(other, "numeric", "__rmatmul__") if other is NotImplemented: return other res = self._array.__rmatmul__(other._array) @@ -758,7 +813,7 @@ def __imod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imod__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__imod__') + other = self._check_allowed_dtypes(other, "numeric", "__imod__") if other is NotImplemented: return other self._array.__imod__(other._array) @@ -768,7 +823,7 @@ def __rmod__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmod__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__rmod__') + other = self._check_allowed_dtypes(other, "numeric", "__rmod__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -779,7 +834,7 @@ def __imul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __imul__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__imul__') + other = self._check_allowed_dtypes(other, "numeric", "__imul__") if other is NotImplemented: return other self._array.__imul__(other._array) @@ -789,7 +844,7 @@ def __rmul__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rmul__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__rmul__') + other = self._check_allowed_dtypes(other, "numeric", "__rmul__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -800,7 +855,7 @@ def __ior__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ior__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__ior__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__ior__") if other is NotImplemented: return other self._array.__ior__(other._array) @@ -810,7 +865,7 @@ def __ror__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ror__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__ror__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__ror__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -821,7 +876,7 @@ def __ipow__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __ipow__. """ - other = self._check_allowed_dtypes(other, 'floating-point', '__ipow__') + other = self._check_allowed_dtypes(other, "floating-point", "__ipow__") if other is NotImplemented: return other self._array.__ipow__(other._array) @@ -833,7 +888,7 @@ def __rpow__(self: Array, other: Union[float, Array], /) -> Array: """ from ._elementwise_functions import pow - other = self._check_allowed_dtypes(other, 'floating-point', '__rpow__') + other = self._check_allowed_dtypes(other, "floating-point", "__rpow__") if other is NotImplemented: return other # Note: NumPy's __pow__ does not follow the spec type promotion rules @@ -844,7 +899,7 @@ def __irshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __irshift__. """ - other = self._check_allowed_dtypes(other, 'integer', '__irshift__') + other = self._check_allowed_dtypes(other, "integer", "__irshift__") if other is NotImplemented: return other self._array.__irshift__(other._array) @@ -854,7 +909,7 @@ def __rrshift__(self: Array, other: Union[int, Array], /) -> Array: """ Performs the operation __rrshift__. """ - other = self._check_allowed_dtypes(other, 'integer', '__rrshift__') + other = self._check_allowed_dtypes(other, "integer", "__rrshift__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -865,7 +920,7 @@ def __isub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __isub__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__isub__') + other = self._check_allowed_dtypes(other, "numeric", "__isub__") if other is NotImplemented: return other self._array.__isub__(other._array) @@ -875,7 +930,7 @@ def __rsub__(self: Array, other: Union[int, float, Array], /) -> Array: """ Performs the operation __rsub__. """ - other = self._check_allowed_dtypes(other, 'numeric', '__rsub__') + other = self._check_allowed_dtypes(other, "numeric", "__rsub__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -886,7 +941,7 @@ def __itruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __itruediv__. """ - other = self._check_allowed_dtypes(other, 'floating-point', '__itruediv__') + other = self._check_allowed_dtypes(other, "floating-point", "__itruediv__") if other is NotImplemented: return other self._array.__itruediv__(other._array) @@ -896,7 +951,7 @@ def __rtruediv__(self: Array, other: Union[float, Array], /) -> Array: """ Performs the operation __rtruediv__. """ - other = self._check_allowed_dtypes(other, 'floating-point', '__rtruediv__') + other = self._check_allowed_dtypes(other, "floating-point", "__rtruediv__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -907,7 +962,7 @@ def __ixor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __ixor__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__ixor__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__ixor__") if other is NotImplemented: return other self._array.__ixor__(other._array) @@ -917,7 +972,7 @@ def __rxor__(self: Array, other: Union[int, bool, Array], /) -> Array: """ Performs the operation __rxor__. """ - other = self._check_allowed_dtypes(other, 'integer or boolean', '__rxor__') + other = self._check_allowed_dtypes(other, "integer or boolean", "__rxor__") if other is NotImplemented: return other self, other = self._normalize_two_args(self, other) @@ -935,7 +990,7 @@ def dtype(self) -> Dtype: @property def device(self) -> Device: - return 'cpu' + return "cpu" @property def ndim(self) -> int: diff --git a/numpy/array_api/_creation_functions.py b/numpy/array_api/_creation_functions.py index acf78056a27..e9c01e7e610 100644 --- a/numpy/array_api/_creation_functions.py +++ b/numpy/array_api/_creation_functions.py @@ -2,14 +2,22 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, Union + if TYPE_CHECKING: - from ._typing import (Array, Device, Dtype, NestedSequence, - SupportsDLPack, SupportsBufferProtocol) + from ._typing import ( + Array, + Device, + Dtype, + NestedSequence, + SupportsDLPack, + SupportsBufferProtocol, + ) from collections.abc import Sequence from ._dtypes import _all_dtypes import numpy as np + def _check_valid_dtype(dtype): # Note: Only spelling dtypes as the dtype objects is supported. @@ -20,7 +28,23 @@ def _check_valid_dtype(dtype): return raise ValueError("dtype must be one of the supported dtypes") -def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], SupportsDLPack, SupportsBufferProtocol], /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, copy: Optional[bool] = None) -> Array: + +def asarray( + obj: Union[ + Array, + bool, + int, + float, + NestedSequence[bool | int | float], + SupportsDLPack, + SupportsBufferProtocol, + ], + /, + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, + copy: Optional[bool] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.asarray `. @@ -31,7 +55,7 @@ def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") if copy is False: # Note: copy=False is not yet implemented in np.asarray @@ -40,14 +64,23 @@ def asarray(obj: Union[Array, bool, int, float, NestedSequence[bool|int|float], if copy is True: return Array._new(np.array(obj._array, copy=True, dtype=dtype)) return obj - if dtype is None and isinstance(obj, int) and (obj > 2**64 or obj < -2**63): + if dtype is None and isinstance(obj, int) and (obj > 2 ** 64 or obj < -(2 ** 63)): # Give a better error message in this case. NumPy would convert this # to an object array. TODO: This won't handle large integers in lists. raise OverflowError("Integer out of bounds for array dtypes") res = np.asarray(obj, dtype=dtype) return Array._new(res) -def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None, step: Union[int, float] = 1, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def arange( + start: Union[int, float], + /, + stop: Optional[Union[int, float]] = None, + step: Union[int, float] = 1, + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.arange `. @@ -56,11 +89,17 @@ def arange(start: Union[int, float], /, stop: Optional[Union[int, float]] = None from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.arange(start, stop=stop, step=step, dtype=dtype)) -def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def empty( + shape: Union[int, Tuple[int, ...]], + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.empty `. @@ -69,11 +108,14 @@ def empty(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.empty(shape, dtype=dtype)) -def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def empty_like( + x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None +) -> Array: """ Array API compatible wrapper for :py:func:`np.empty_like `. @@ -82,11 +124,20 @@ def empty_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.empty_like(x._array, dtype=dtype)) -def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def eye( + n_rows: int, + n_cols: Optional[int] = None, + /, + *, + k: Optional[int] = 0, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.eye `. @@ -95,15 +146,23 @@ def eye(n_rows: int, n_cols: Optional[int] = None, /, *, k: Optional[int] = 0, d from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.eye(n_rows, M=n_cols, k=k, dtype=dtype)) + def from_dlpack(x: object, /) -> Array: # Note: dlpack support is not yet implemented on Array raise NotImplementedError("DLPack support is not yet implemented") -def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def full( + shape: Union[int, Tuple[int, ...]], + fill_value: Union[int, float], + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.full `. @@ -112,7 +171,7 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") if isinstance(fill_value, Array) and fill_value.ndim == 0: fill_value = fill_value._array @@ -123,7 +182,15 @@ def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[int, float], *, d raise TypeError("Invalid input to full") return Array._new(res) -def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def full_like( + x: Array, + /, + fill_value: Union[int, float], + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.full_like `. @@ -132,7 +199,7 @@ def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dty from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") res = np.full_like(x._array, fill_value, dtype=dtype) if res.dtype not in _all_dtypes: @@ -141,7 +208,17 @@ def full_like(x: Array, /, fill_value: Union[int, float], *, dtype: Optional[Dty raise TypeError("Invalid input to full_like") return Array._new(res) -def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None, endpoint: bool = True) -> Array: + +def linspace( + start: Union[int, float], + stop: Union[int, float], + /, + num: int, + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, + endpoint: bool = True, +) -> Array: """ Array API compatible wrapper for :py:func:`np.linspace `. @@ -150,20 +227,31 @@ def linspace(start: Union[int, float], stop: Union[int, float], /, num: int, *, from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.linspace(start, stop, num, dtype=dtype, endpoint=endpoint)) -def meshgrid(*arrays: Sequence[Array], indexing: str = 'xy') -> List[Array, ...]: + +def meshgrid(*arrays: Sequence[Array], indexing: str = "xy") -> List[Array, ...]: """ Array API compatible wrapper for :py:func:`np.meshgrid `. See its docstring for more information. """ from ._array_object import Array - return [Array._new(array) for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)] -def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + return [ + Array._new(array) + for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing) + ] + + +def ones( + shape: Union[int, Tuple[int, ...]], + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.ones `. @@ -172,11 +260,14 @@ def ones(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, d from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.ones(shape, dtype=dtype)) -def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def ones_like( + x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None +) -> Array: """ Array API compatible wrapper for :py:func:`np.ones_like `. @@ -185,11 +276,17 @@ def ones_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[De from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.ones_like(x._array, dtype=dtype)) -def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def zeros( + shape: Union[int, Tuple[int, ...]], + *, + dtype: Optional[Dtype] = None, + device: Optional[Device] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.zeros `. @@ -198,11 +295,14 @@ def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: Optional[Dtype] = None, from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.zeros(shape, dtype=dtype)) -def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None) -> Array: + +def zeros_like( + x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[Device] = None +) -> Array: """ Array API compatible wrapper for :py:func:`np.zeros_like `. @@ -211,6 +311,6 @@ def zeros_like(x: Array, /, *, dtype: Optional[Dtype] = None, device: Optional[D from ._array_object import Array _check_valid_dtype(dtype) - if device not in ['cpu', None]: + if device not in ["cpu", None]: raise ValueError(f"Unsupported device {device!r}") return Array._new(np.zeros_like(x._array, dtype=dtype)) diff --git a/numpy/array_api/_data_type_functions.py b/numpy/array_api/_data_type_functions.py index 17a00cc6d07..e6121a8a4e4 100644 --- a/numpy/array_api/_data_type_functions.py +++ b/numpy/array_api/_data_type_functions.py @@ -5,12 +5,14 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, List, Tuple, Union + if TYPE_CHECKING: from ._typing import Dtype from collections.abc import Sequence import numpy as np + def broadcast_arrays(*arrays: Sequence[Array]) -> List[Array]: """ Array API compatible wrapper for :py:func:`np.broadcast_arrays `. @@ -18,7 +20,11 @@ def broadcast_arrays(*arrays: Sequence[Array]) -> List[Array]: See its docstring for more information. """ from ._array_object import Array - return [Array._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays])] + + return [ + Array._new(array) for array in np.broadcast_arrays(*[a._array for a in arrays]) + ] + def broadcast_to(x: Array, /, shape: Tuple[int, ...]) -> Array: """ @@ -27,8 +33,10 @@ def broadcast_to(x: Array, /, shape: Tuple[int, ...]) -> Array: See its docstring for more information. """ from ._array_object import Array + return Array._new(np.broadcast_to(x._array, shape)) + def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: """ Array API compatible wrapper for :py:func:`np.can_cast `. @@ -36,10 +44,12 @@ def can_cast(from_: Union[Dtype, Array], to: Dtype, /) -> bool: See its docstring for more information. """ from ._array_object import Array + if isinstance(from_, Array): from_ = from_._array return np.can_cast(from_, to) + # These are internal objects for the return types of finfo and iinfo, since # the NumPy versions contain extra data that isn't part of the spec. @dataclass @@ -55,12 +65,14 @@ class finfo_object: # smallest_normal: float + @dataclass class iinfo_object: bits: int max: int min: int + def finfo(type: Union[Dtype, Array], /) -> finfo_object: """ Array API compatible wrapper for :py:func:`np.finfo `. @@ -79,6 +91,7 @@ def finfo(type: Union[Dtype, Array], /) -> finfo_object: # float(fi.smallest_normal), ) + def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: """ Array API compatible wrapper for :py:func:`np.iinfo `. @@ -88,6 +101,7 @@ def iinfo(type: Union[Dtype, Array], /) -> iinfo_object: ii = np.iinfo(type) return iinfo_object(ii.bits, ii.max, ii.min) + def result_type(*arrays_and_dtypes: Sequence[Union[Array, Dtype]]) -> Dtype: """ Array API compatible wrapper for :py:func:`np.result_type `. diff --git a/numpy/array_api/_dtypes.py b/numpy/array_api/_dtypes.py index 07be267da20..476d619fee6 100644 --- a/numpy/array_api/_dtypes.py +++ b/numpy/array_api/_dtypes.py @@ -2,34 +2,66 @@ # Note: we use dtype objects instead of dtype classes. The spec does not # require any behavior on dtypes other than equality. -int8 = np.dtype('int8') -int16 = np.dtype('int16') -int32 = np.dtype('int32') -int64 = np.dtype('int64') -uint8 = np.dtype('uint8') -uint16 = np.dtype('uint16') -uint32 = np.dtype('uint32') -uint64 = np.dtype('uint64') -float32 = np.dtype('float32') -float64 = np.dtype('float64') +int8 = np.dtype("int8") +int16 = np.dtype("int16") +int32 = np.dtype("int32") +int64 = np.dtype("int64") +uint8 = np.dtype("uint8") +uint16 = np.dtype("uint16") +uint32 = np.dtype("uint32") +uint64 = np.dtype("uint64") +float32 = np.dtype("float32") +float64 = np.dtype("float64") # Note: This name is changed -bool = np.dtype('bool') +bool = np.dtype("bool") -_all_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64, - float32, float64, bool) +_all_dtypes = ( + int8, + int16, + int32, + int64, + uint8, + uint16, + uint32, + uint64, + float32, + float64, + bool, +) _boolean_dtypes = (bool,) _floating_dtypes = (float32, float64) _integer_dtypes = (int8, int16, int32, int64, uint8, uint16, uint32, uint64) -_integer_or_boolean_dtypes = (bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64) -_numeric_dtypes = (float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64) +_integer_or_boolean_dtypes = ( + bool, + int8, + int16, + int32, + int64, + uint8, + uint16, + uint32, + uint64, +) +_numeric_dtypes = ( + float32, + float64, + int8, + int16, + int32, + int64, + uint8, + uint16, + uint32, + uint64, +) _dtype_categories = { - 'all': _all_dtypes, - 'numeric': _numeric_dtypes, - 'integer': _integer_dtypes, - 'integer or boolean': _integer_or_boolean_dtypes, - 'boolean': _boolean_dtypes, - 'floating-point': _floating_dtypes, + "all": _all_dtypes, + "numeric": _numeric_dtypes, + "integer": _integer_dtypes, + "integer or boolean": _integer_or_boolean_dtypes, + "boolean": _boolean_dtypes, + "floating-point": _floating_dtypes, } @@ -104,6 +136,7 @@ (bool, bool): bool, } + def _result_type(type1, type2): if (type1, type2) in _promotion_table: return _promotion_table[type1, type2] diff --git a/numpy/array_api/_elementwise_functions.py b/numpy/array_api/_elementwise_functions.py index 7833ebe54d5..4408fe833b4 100644 --- a/numpy/array_api/_elementwise_functions.py +++ b/numpy/array_api/_elementwise_functions.py @@ -1,12 +1,18 @@ from __future__ import annotations -from ._dtypes import (_boolean_dtypes, _floating_dtypes, - _integer_dtypes, _integer_or_boolean_dtypes, - _numeric_dtypes, _result_type) +from ._dtypes import ( + _boolean_dtypes, + _floating_dtypes, + _integer_dtypes, + _integer_or_boolean_dtypes, + _numeric_dtypes, + _result_type, +) from ._array_object import Array import numpy as np + def abs(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.abs `. @@ -14,9 +20,10 @@ def abs(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in abs') + raise TypeError("Only numeric dtypes are allowed in abs") return Array._new(np.abs(x._array)) + # Note: the function name is different here def acos(x: Array, /) -> Array: """ @@ -25,9 +32,10 @@ def acos(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in acos') + raise TypeError("Only floating-point dtypes are allowed in acos") return Array._new(np.arccos(x._array)) + # Note: the function name is different here def acosh(x: Array, /) -> Array: """ @@ -36,9 +44,10 @@ def acosh(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in acosh') + raise TypeError("Only floating-point dtypes are allowed in acosh") return Array._new(np.arccosh(x._array)) + def add(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.add `. @@ -46,12 +55,13 @@ def add(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in add') + raise TypeError("Only numeric dtypes are allowed in add") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.add(x1._array, x2._array)) + # Note: the function name is different here def asin(x: Array, /) -> Array: """ @@ -60,9 +70,10 @@ def asin(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in asin') + raise TypeError("Only floating-point dtypes are allowed in asin") return Array._new(np.arcsin(x._array)) + # Note: the function name is different here def asinh(x: Array, /) -> Array: """ @@ -71,9 +82,10 @@ def asinh(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in asinh') + raise TypeError("Only floating-point dtypes are allowed in asinh") return Array._new(np.arcsinh(x._array)) + # Note: the function name is different here def atan(x: Array, /) -> Array: """ @@ -82,9 +94,10 @@ def atan(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in atan') + raise TypeError("Only floating-point dtypes are allowed in atan") return Array._new(np.arctan(x._array)) + # Note: the function name is different here def atan2(x1: Array, x2: Array, /) -> Array: """ @@ -93,12 +106,13 @@ def atan2(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in atan2') + raise TypeError("Only floating-point dtypes are allowed in atan2") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.arctan2(x1._array, x2._array)) + # Note: the function name is different here def atanh(x: Array, /) -> Array: """ @@ -107,22 +121,27 @@ def atanh(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in atanh') + raise TypeError("Only floating-point dtypes are allowed in atanh") return Array._new(np.arctanh(x._array)) + def bitwise_and(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.bitwise_and `. See its docstring for more information. """ - if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: - raise TypeError('Only integer or boolean dtypes are allowed in bitwise_and') + if ( + x1.dtype not in _integer_or_boolean_dtypes + or x2.dtype not in _integer_or_boolean_dtypes + ): + raise TypeError("Only integer or boolean dtypes are allowed in bitwise_and") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_and(x1._array, x2._array)) + # Note: the function name is different here def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: """ @@ -131,15 +150,16 @@ def bitwise_left_shift(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: - raise TypeError('Only integer dtypes are allowed in bitwise_left_shift') + raise TypeError("Only integer dtypes are allowed in bitwise_left_shift") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) # Note: bitwise_left_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): - raise ValueError('bitwise_left_shift(x1, x2) is only defined for x2 >= 0') + raise ValueError("bitwise_left_shift(x1, x2) is only defined for x2 >= 0") return Array._new(np.left_shift(x1._array, x2._array)) + # Note: the function name is different here def bitwise_invert(x: Array, /) -> Array: """ @@ -148,22 +168,27 @@ def bitwise_invert(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _integer_or_boolean_dtypes: - raise TypeError('Only integer or boolean dtypes are allowed in bitwise_invert') + raise TypeError("Only integer or boolean dtypes are allowed in bitwise_invert") return Array._new(np.invert(x._array)) + def bitwise_or(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.bitwise_or `. See its docstring for more information. """ - if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: - raise TypeError('Only integer or boolean dtypes are allowed in bitwise_or') + if ( + x1.dtype not in _integer_or_boolean_dtypes + or x2.dtype not in _integer_or_boolean_dtypes + ): + raise TypeError("Only integer or boolean dtypes are allowed in bitwise_or") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_or(x1._array, x2._array)) + # Note: the function name is different here def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: """ @@ -172,28 +197,33 @@ def bitwise_right_shift(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _integer_dtypes or x2.dtype not in _integer_dtypes: - raise TypeError('Only integer dtypes are allowed in bitwise_right_shift') + raise TypeError("Only integer dtypes are allowed in bitwise_right_shift") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) # Note: bitwise_right_shift is only defined for x2 nonnegative. if np.any(x2._array < 0): - raise ValueError('bitwise_right_shift(x1, x2) is only defined for x2 >= 0') + raise ValueError("bitwise_right_shift(x1, x2) is only defined for x2 >= 0") return Array._new(np.right_shift(x1._array, x2._array)) + def bitwise_xor(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.bitwise_xor `. See its docstring for more information. """ - if x1.dtype not in _integer_or_boolean_dtypes or x2.dtype not in _integer_or_boolean_dtypes: - raise TypeError('Only integer or boolean dtypes are allowed in bitwise_xor') + if ( + x1.dtype not in _integer_or_boolean_dtypes + or x2.dtype not in _integer_or_boolean_dtypes + ): + raise TypeError("Only integer or boolean dtypes are allowed in bitwise_xor") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.bitwise_xor(x1._array, x2._array)) + def ceil(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.ceil `. @@ -201,12 +231,13 @@ def ceil(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in ceil') + raise TypeError("Only numeric dtypes are allowed in ceil") if x.dtype in _integer_dtypes: # Note: The return dtype of ceil is the same as the input return x return Array._new(np.ceil(x._array)) + def cos(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.cos `. @@ -214,9 +245,10 @@ def cos(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in cos') + raise TypeError("Only floating-point dtypes are allowed in cos") return Array._new(np.cos(x._array)) + def cosh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.cosh `. @@ -224,9 +256,10 @@ def cosh(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in cosh') + raise TypeError("Only floating-point dtypes are allowed in cosh") return Array._new(np.cosh(x._array)) + def divide(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.divide `. @@ -234,12 +267,13 @@ def divide(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in divide') + raise TypeError("Only floating-point dtypes are allowed in divide") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.divide(x1._array, x2._array)) + def equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.equal `. @@ -251,6 +285,7 @@ def equal(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.equal(x1._array, x2._array)) + def exp(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.exp `. @@ -258,9 +293,10 @@ def exp(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in exp') + raise TypeError("Only floating-point dtypes are allowed in exp") return Array._new(np.exp(x._array)) + def expm1(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.expm1 `. @@ -268,9 +304,10 @@ def expm1(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in expm1') + raise TypeError("Only floating-point dtypes are allowed in expm1") return Array._new(np.expm1(x._array)) + def floor(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.floor `. @@ -278,12 +315,13 @@ def floor(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in floor') + raise TypeError("Only numeric dtypes are allowed in floor") if x.dtype in _integer_dtypes: # Note: The return dtype of floor is the same as the input return x return Array._new(np.floor(x._array)) + def floor_divide(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.floor_divide `. @@ -291,12 +329,13 @@ def floor_divide(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in floor_divide') + raise TypeError("Only numeric dtypes are allowed in floor_divide") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.floor_divide(x1._array, x2._array)) + def greater(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.greater `. @@ -304,12 +343,13 @@ def greater(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in greater') + raise TypeError("Only numeric dtypes are allowed in greater") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.greater(x1._array, x2._array)) + def greater_equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.greater_equal `. @@ -317,12 +357,13 @@ def greater_equal(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in greater_equal') + raise TypeError("Only numeric dtypes are allowed in greater_equal") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.greater_equal(x1._array, x2._array)) + def isfinite(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.isfinite `. @@ -330,9 +371,10 @@ def isfinite(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in isfinite') + raise TypeError("Only numeric dtypes are allowed in isfinite") return Array._new(np.isfinite(x._array)) + def isinf(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.isinf `. @@ -340,9 +382,10 @@ def isinf(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in isinf') + raise TypeError("Only numeric dtypes are allowed in isinf") return Array._new(np.isinf(x._array)) + def isnan(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.isnan `. @@ -350,9 +393,10 @@ def isnan(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in isnan') + raise TypeError("Only numeric dtypes are allowed in isnan") return Array._new(np.isnan(x._array)) + def less(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.less `. @@ -360,12 +404,13 @@ def less(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in less') + raise TypeError("Only numeric dtypes are allowed in less") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.less(x1._array, x2._array)) + def less_equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.less_equal `. @@ -373,12 +418,13 @@ def less_equal(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in less_equal') + raise TypeError("Only numeric dtypes are allowed in less_equal") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.less_equal(x1._array, x2._array)) + def log(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log `. @@ -386,9 +432,10 @@ def log(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in log') + raise TypeError("Only floating-point dtypes are allowed in log") return Array._new(np.log(x._array)) + def log1p(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log1p `. @@ -396,9 +443,10 @@ def log1p(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in log1p') + raise TypeError("Only floating-point dtypes are allowed in log1p") return Array._new(np.log1p(x._array)) + def log2(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log2 `. @@ -406,9 +454,10 @@ def log2(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in log2') + raise TypeError("Only floating-point dtypes are allowed in log2") return Array._new(np.log2(x._array)) + def log10(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.log10 `. @@ -416,9 +465,10 @@ def log10(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in log10') + raise TypeError("Only floating-point dtypes are allowed in log10") return Array._new(np.log10(x._array)) + def logaddexp(x1: Array, x2: Array) -> Array: """ Array API compatible wrapper for :py:func:`np.logaddexp `. @@ -426,12 +476,13 @@ def logaddexp(x1: Array, x2: Array) -> Array: See its docstring for more information. """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in logaddexp') + raise TypeError("Only floating-point dtypes are allowed in logaddexp") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logaddexp(x1._array, x2._array)) + def logical_and(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_and `. @@ -439,12 +490,13 @@ def logical_and(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: - raise TypeError('Only boolean dtypes are allowed in logical_and') + raise TypeError("Only boolean dtypes are allowed in logical_and") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_and(x1._array, x2._array)) + def logical_not(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_not `. @@ -452,9 +504,10 @@ def logical_not(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _boolean_dtypes: - raise TypeError('Only boolean dtypes are allowed in logical_not') + raise TypeError("Only boolean dtypes are allowed in logical_not") return Array._new(np.logical_not(x._array)) + def logical_or(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_or `. @@ -462,12 +515,13 @@ def logical_or(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: - raise TypeError('Only boolean dtypes are allowed in logical_or') + raise TypeError("Only boolean dtypes are allowed in logical_or") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_or(x1._array, x2._array)) + def logical_xor(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.logical_xor `. @@ -475,12 +529,13 @@ def logical_xor(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _boolean_dtypes or x2.dtype not in _boolean_dtypes: - raise TypeError('Only boolean dtypes are allowed in logical_xor') + raise TypeError("Only boolean dtypes are allowed in logical_xor") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.logical_xor(x1._array, x2._array)) + def multiply(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.multiply `. @@ -488,12 +543,13 @@ def multiply(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in multiply') + raise TypeError("Only numeric dtypes are allowed in multiply") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.multiply(x1._array, x2._array)) + def negative(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.negative `. @@ -501,9 +557,10 @@ def negative(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in negative') + raise TypeError("Only numeric dtypes are allowed in negative") return Array._new(np.negative(x._array)) + def not_equal(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.not_equal `. @@ -515,6 +572,7 @@ def not_equal(x1: Array, x2: Array, /) -> Array: x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.not_equal(x1._array, x2._array)) + def positive(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.positive `. @@ -522,9 +580,10 @@ def positive(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in positive') + raise TypeError("Only numeric dtypes are allowed in positive") return Array._new(np.positive(x._array)) + # Note: the function name is different here def pow(x1: Array, x2: Array, /) -> Array: """ @@ -533,12 +592,13 @@ def pow(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _floating_dtypes or x2.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in pow') + raise TypeError("Only floating-point dtypes are allowed in pow") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.power(x1._array, x2._array)) + def remainder(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.remainder `. @@ -546,12 +606,13 @@ def remainder(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in remainder') + raise TypeError("Only numeric dtypes are allowed in remainder") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.remainder(x1._array, x2._array)) + def round(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.round `. @@ -559,9 +620,10 @@ def round(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in round') + raise TypeError("Only numeric dtypes are allowed in round") return Array._new(np.round(x._array)) + def sign(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sign `. @@ -569,9 +631,10 @@ def sign(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in sign') + raise TypeError("Only numeric dtypes are allowed in sign") return Array._new(np.sign(x._array)) + def sin(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sin `. @@ -579,9 +642,10 @@ def sin(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in sin') + raise TypeError("Only floating-point dtypes are allowed in sin") return Array._new(np.sin(x._array)) + def sinh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sinh `. @@ -589,9 +653,10 @@ def sinh(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in sinh') + raise TypeError("Only floating-point dtypes are allowed in sinh") return Array._new(np.sinh(x._array)) + def square(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.square `. @@ -599,9 +664,10 @@ def square(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in square') + raise TypeError("Only numeric dtypes are allowed in square") return Array._new(np.square(x._array)) + def sqrt(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.sqrt `. @@ -609,9 +675,10 @@ def sqrt(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in sqrt') + raise TypeError("Only floating-point dtypes are allowed in sqrt") return Array._new(np.sqrt(x._array)) + def subtract(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.subtract `. @@ -619,12 +686,13 @@ def subtract(x1: Array, x2: Array, /) -> Array: See its docstring for more information. """ if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in subtract') + raise TypeError("Only numeric dtypes are allowed in subtract") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) x1, x2 = Array._normalize_two_args(x1, x2) return Array._new(np.subtract(x1._array, x2._array)) + def tan(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.tan `. @@ -632,9 +700,10 @@ def tan(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in tan') + raise TypeError("Only floating-point dtypes are allowed in tan") return Array._new(np.tan(x._array)) + def tanh(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.tanh `. @@ -642,9 +711,10 @@ def tanh(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _floating_dtypes: - raise TypeError('Only floating-point dtypes are allowed in tanh') + raise TypeError("Only floating-point dtypes are allowed in tanh") return Array._new(np.tanh(x._array)) + def trunc(x: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.trunc `. @@ -652,7 +722,7 @@ def trunc(x: Array, /) -> Array: See its docstring for more information. """ if x.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in trunc') + raise TypeError("Only numeric dtypes are allowed in trunc") if x.dtype in _integer_dtypes: # Note: The return dtype of trunc is the same as the input return x diff --git a/numpy/array_api/_linear_algebra_functions.py b/numpy/array_api/_linear_algebra_functions.py index f13f9c54169..089081725cc 100644 --- a/numpy/array_api/_linear_algebra_functions.py +++ b/numpy/array_api/_linear_algebra_functions.py @@ -17,6 +17,7 @@ # """ # return np.einsum() + def matmul(x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.matmul `. @@ -26,23 +27,31 @@ def matmul(x1: Array, x2: Array, /) -> Array: # Note: the restriction to numeric dtypes only is different from # np.matmul. if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in matmul') + raise TypeError("Only numeric dtypes are allowed in matmul") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) return Array._new(np.matmul(x1._array, x2._array)) + # Note: axes must be a tuple, unlike np.tensordot where it can be an array or array-like. -def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> Array: +def tensordot( + x1: Array, + x2: Array, + /, + *, + axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2, +) -> Array: # Note: the restriction to numeric dtypes only is different from # np.tensordot. if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes: - raise TypeError('Only numeric dtypes are allowed in tensordot') + raise TypeError("Only numeric dtypes are allowed in tensordot") # Call result type here just to raise on disallowed type combinations _result_type(x1.dtype, x2.dtype) return Array._new(np.tensordot(x1._array, x2._array, axes=axes)) + def transpose(x: Array, /, *, axes: Optional[Tuple[int, ...]] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.transpose `. @@ -51,6 +60,7 @@ def transpose(x: Array, /, *, axes: Optional[Tuple[int, ...]] = None) -> Array: """ return Array._new(np.transpose(x._array, axes=axes)) + # Note: vecdot is not in NumPy def vecdot(x1: Array, x2: Array, /, *, axis: Optional[int] = None) -> Array: if axis is None: diff --git a/numpy/array_api/_manipulation_functions.py b/numpy/array_api/_manipulation_functions.py index 33f5d5a28c3..c11866261f9 100644 --- a/numpy/array_api/_manipulation_functions.py +++ b/numpy/array_api/_manipulation_functions.py @@ -8,7 +8,9 @@ import numpy as np # Note: the function name is different here -def concat(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[int] = 0) -> Array: +def concat( + arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[int] = 0 +) -> Array: """ Array API compatible wrapper for :py:func:`np.concatenate `. @@ -20,6 +22,7 @@ def concat(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[i arrays = tuple(a._array for a in arrays) return Array._new(np.concatenate(arrays, axis=axis, dtype=dtype)) + def expand_dims(x: Array, /, *, axis: int) -> Array: """ Array API compatible wrapper for :py:func:`np.expand_dims `. @@ -28,6 +31,7 @@ def expand_dims(x: Array, /, *, axis: int) -> Array: """ return Array._new(np.expand_dims(x._array, axis)) + def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: """ Array API compatible wrapper for :py:func:`np.flip `. @@ -36,6 +40,7 @@ def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> """ return Array._new(np.flip(x._array, axis=axis)) + def reshape(x: Array, /, shape: Tuple[int, ...]) -> Array: """ Array API compatible wrapper for :py:func:`np.reshape `. @@ -44,7 +49,14 @@ def reshape(x: Array, /, shape: Tuple[int, ...]) -> Array: """ return Array._new(np.reshape(x._array, shape)) -def roll(x: Array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array: + +def roll( + x: Array, + /, + shift: Union[int, Tuple[int, ...]], + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, +) -> Array: """ Array API compatible wrapper for :py:func:`np.roll `. @@ -52,6 +64,7 @@ def roll(x: Array, /, shift: Union[int, Tuple[int, ...]], *, axis: Optional[Unio """ return Array._new(np.roll(x._array, shift, axis=axis)) + def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array: """ Array API compatible wrapper for :py:func:`np.squeeze `. @@ -60,6 +73,7 @@ def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array: """ return Array._new(np.squeeze(x._array, axis=axis)) + def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) -> Array: """ Array API compatible wrapper for :py:func:`np.stack `. diff --git a/numpy/array_api/_searching_functions.py b/numpy/array_api/_searching_functions.py index 9dcc76b2de8..3dcef61c3bc 100644 --- a/numpy/array_api/_searching_functions.py +++ b/numpy/array_api/_searching_functions.py @@ -7,6 +7,7 @@ import numpy as np + def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.argmax `. @@ -15,6 +16,7 @@ def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) - """ return Array._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims))) + def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array: """ Array API compatible wrapper for :py:func:`np.argmin `. @@ -23,6 +25,7 @@ def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) - """ return Array._new(np.asarray(np.argmin(x._array, axis=axis, keepdims=keepdims))) + def nonzero(x: Array, /) -> Tuple[Array, ...]: """ Array API compatible wrapper for :py:func:`np.nonzero `. @@ -31,6 +34,7 @@ def nonzero(x: Array, /) -> Tuple[Array, ...]: """ return tuple(Array._new(i) for i in np.nonzero(x._array)) + def where(condition: Array, x1: Array, x2: Array, /) -> Array: """ Array API compatible wrapper for :py:func:`np.where `. diff --git a/numpy/array_api/_set_functions.py b/numpy/array_api/_set_functions.py index acd59f59734..357f238f5e3 100644 --- a/numpy/array_api/_set_functions.py +++ b/numpy/array_api/_set_functions.py @@ -6,14 +6,26 @@ import numpy as np -def unique(x: Array, /, *, return_counts: bool = False, return_index: bool = False, return_inverse: bool = False) -> Union[Array, Tuple[Array, ...]]: + +def unique( + x: Array, + /, + *, + return_counts: bool = False, + return_index: bool = False, + return_inverse: bool = False, +) -> Union[Array, Tuple[Array, ...]]: """ Array API compatible wrapper for :py:func:`np.unique `. See its docstring for more information. """ - res = np.unique(x._array, return_counts=return_counts, - return_index=return_index, return_inverse=return_inverse) + res = np.unique( + x._array, + return_counts=return_counts, + return_index=return_index, + return_inverse=return_inverse, + ) if isinstance(res, tuple): return tuple(Array._new(i) for i in res) return Array._new(res) diff --git a/numpy/array_api/_sorting_functions.py b/numpy/array_api/_sorting_functions.py index a125e071832..9cd49786cb8 100644 --- a/numpy/array_api/_sorting_functions.py +++ b/numpy/array_api/_sorting_functions.py @@ -4,27 +4,33 @@ import numpy as np -def argsort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: + +def argsort( + x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True +) -> Array: """ Array API compatible wrapper for :py:func:`np.argsort `. See its docstring for more information. """ # Note: this keyword argument is different, and the default is different. - kind = 'stable' if stable else 'quicksort' + kind = "stable" if stable else "quicksort" res = np.argsort(x._array, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) return Array._new(res) -def sort(x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True) -> Array: + +def sort( + x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True +) -> Array: """ Array API compatible wrapper for :py:func:`np.sort `. See its docstring for more information. """ # Note: this keyword argument is different, and the default is different. - kind = 'stable' if stable else 'quicksort' + kind = "stable" if stable else "quicksort" res = np.sort(x._array, axis=axis, kind=kind) if descending: res = np.flip(res, axis=axis) diff --git a/numpy/array_api/_statistical_functions.py b/numpy/array_api/_statistical_functions.py index a606203bc99..63790b4470e 100644 --- a/numpy/array_api/_statistical_functions.py +++ b/numpy/array_api/_statistical_functions.py @@ -6,25 +6,76 @@ import numpy as np -def max(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def max( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: return Array._new(np.max(x._array, axis=axis, keepdims=keepdims)) -def mean(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def mean( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: return Array._new(np.mean(x._array, axis=axis, keepdims=keepdims)) -def min(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def min( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: return Array._new(np.min(x._array, axis=axis, keepdims=keepdims)) -def prod(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def prod( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: return Array._new(np.prod(x._array, axis=axis, keepdims=keepdims)) -def std(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: + +def std( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + correction: Union[int, float] = 0.0, + keepdims: bool = False, +) -> Array: # Note: the keyword argument correction is different here return Array._new(np.std(x._array, axis=axis, ddof=correction, keepdims=keepdims)) -def sum(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def sum( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: return Array._new(np.sum(x._array, axis=axis, keepdims=keepdims)) -def var(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, correction: Union[int, float] = 0.0, keepdims: bool = False) -> Array: + +def var( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + correction: Union[int, float] = 0.0, + keepdims: bool = False, +) -> Array: # Note: the keyword argument correction is different here return Array._new(np.var(x._array, axis=axis, ddof=correction, keepdims=keepdims)) diff --git a/numpy/array_api/_typing.py b/numpy/array_api/_typing.py index 4ff718205dd..d530a91ae72 100644 --- a/numpy/array_api/_typing.py +++ b/numpy/array_api/_typing.py @@ -6,21 +6,39 @@ valid for inputs that match the given type annotations. """ -__all__ = ['Array', 'Device', 'Dtype', 'SupportsDLPack', - 'SupportsBufferProtocol', 'PyCapsule'] +__all__ = [ + "Array", + "Device", + "Dtype", + "SupportsDLPack", + "SupportsBufferProtocol", + "PyCapsule", +] from typing import Any, Sequence, Type, Union -from . import (Array, int8, int16, int32, int64, uint8, uint16, uint32, - uint64, float32, float64) +from . import ( + Array, + int8, + int16, + int32, + int64, + uint8, + uint16, + uint32, + uint64, + float32, + float64, +) # This should really be recursive, but that isn't supported yet. See the # similar comment in numpy/typing/_array_like.py NestedSequence = Sequence[Sequence[Any]] Device = Any -Dtype = Type[Union[[int8, int16, int32, int64, uint8, uint16, - uint32, uint64, float32, float64]]] +Dtype = Type[ + Union[[int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64]] +] SupportsDLPack = Any SupportsBufferProtocol = Any PyCapsule = Any diff --git a/numpy/array_api/_utility_functions.py b/numpy/array_api/_utility_functions.py index f243bfe684d..5ecb4bd9fef 100644 --- a/numpy/array_api/_utility_functions.py +++ b/numpy/array_api/_utility_functions.py @@ -6,7 +6,14 @@ import numpy as np -def all(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def all( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: """ Array API compatible wrapper for :py:func:`np.all `. @@ -14,7 +21,14 @@ def all(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep """ return Array._new(np.asarray(np.all(x._array, axis=axis, keepdims=keepdims))) -def any(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False) -> Array: + +def any( + x: Array, + /, + *, + axis: Optional[Union[int, Tuple[int, ...]]] = None, + keepdims: bool = False, +) -> Array: """ Array API compatible wrapper for :py:func:`np.any `. diff --git a/numpy/array_api/setup.py b/numpy/array_api/setup.py index da2350c8f0d..c8bc2910268 100644 --- a/numpy/array_api/setup.py +++ b/numpy/array_api/setup.py @@ -1,10 +1,12 @@ -def configuration(parent_package='', top_path=None): +def configuration(parent_package="", top_path=None): from numpy.distutils.misc_util import Configuration - config = Configuration('array_api', parent_package, top_path) - config.add_subpackage('tests') + + config = Configuration("array_api", parent_package, top_path) + config.add_subpackage("tests") return config -if __name__ == '__main__': +if __name__ == "__main__": from numpy.distutils.core import setup + setup(configuration=configuration) diff --git a/numpy/array_api/tests/test_array_object.py b/numpy/array_api/tests/test_array_object.py index 22078bbee7b..088e09b9f21 100644 --- a/numpy/array_api/tests/test_array_object.py +++ b/numpy/array_api/tests/test_array_object.py @@ -2,9 +2,20 @@ import numpy as np from .. import ones, asarray, result_type -from .._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, - _integer_dtypes, _integer_or_boolean_dtypes, - _numeric_dtypes, int8, int16, int32, int64, uint64) +from .._dtypes import ( + _all_dtypes, + _boolean_dtypes, + _floating_dtypes, + _integer_dtypes, + _integer_or_boolean_dtypes, + _numeric_dtypes, + int8, + int16, + int32, + int64, + uint64, +) + def test_validate_index(): # The indexing tests in the official array API test suite test that the @@ -61,28 +72,29 @@ def test_validate_index(): assert_raises(IndexError, lambda: a[None, ...]) assert_raises(IndexError, lambda: a[..., None]) + def test_operators(): # For every operator, we test that it works for the required type # combinations and raises TypeError otherwise - binary_op_dtypes ={ - '__add__': 'numeric', - '__and__': 'integer_or_boolean', - '__eq__': 'all', - '__floordiv__': 'numeric', - '__ge__': 'numeric', - '__gt__': 'numeric', - '__le__': 'numeric', - '__lshift__': 'integer', - '__lt__': 'numeric', - '__mod__': 'numeric', - '__mul__': 'numeric', - '__ne__': 'all', - '__or__': 'integer_or_boolean', - '__pow__': 'floating', - '__rshift__': 'integer', - '__sub__': 'numeric', - '__truediv__': 'floating', - '__xor__': 'integer_or_boolean', + binary_op_dtypes = { + "__add__": "numeric", + "__and__": "integer_or_boolean", + "__eq__": "all", + "__floordiv__": "numeric", + "__ge__": "numeric", + "__gt__": "numeric", + "__le__": "numeric", + "__lshift__": "integer", + "__lt__": "numeric", + "__mod__": "numeric", + "__mul__": "numeric", + "__ne__": "all", + "__or__": "integer_or_boolean", + "__pow__": "floating", + "__rshift__": "integer", + "__sub__": "numeric", + "__truediv__": "floating", + "__xor__": "integer_or_boolean", } # Recompute each time because of in-place ops @@ -92,15 +104,15 @@ def _array_vals(): for d in _boolean_dtypes: yield asarray(False, dtype=d) for d in _floating_dtypes: - yield asarray(1., dtype=d) + yield asarray(1.0, dtype=d) for op, dtypes in binary_op_dtypes.items(): ops = [op] - if op not in ['__eq__', '__ne__', '__le__', '__ge__', '__lt__', '__gt__']: - rop = '__r' + op[2:] - iop = '__i' + op[2:] + if op not in ["__eq__", "__ne__", "__le__", "__ge__", "__lt__", "__gt__"]: + rop = "__r" + op[2:] + iop = "__i" + op[2:] ops += [rop, iop] - for s in [1, 1., False]: + for s in [1, 1.0, False]: for _op in ops: for a in _array_vals(): # Test array op scalar. From the spec, the following combinations @@ -149,7 +161,10 @@ def _array_vals(): ): assert_raises(TypeError, lambda: getattr(x, _op)(y)) # Ensure in-place operators only promote to the same dtype as the left operand. - elif _op.startswith('__i') and result_type(x.dtype, y.dtype) != x.dtype: + elif ( + _op.startswith("__i") + and result_type(x.dtype, y.dtype) != x.dtype + ): assert_raises(TypeError, lambda: getattr(x, _op)(y)) # Ensure only those dtypes that are required for every operator are allowed. elif (dtypes == "all" and (x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes @@ -165,17 +180,20 @@ def _array_vals(): else: assert_raises(TypeError, lambda: getattr(x, _op)(y)) - unary_op_dtypes ={ - '__abs__': 'numeric', - '__invert__': 'integer_or_boolean', - '__neg__': 'numeric', - '__pos__': 'numeric', + unary_op_dtypes = { + "__abs__": "numeric", + "__invert__": "integer_or_boolean", + "__neg__": "numeric", + "__pos__": "numeric", } for op, dtypes in unary_op_dtypes.items(): for a in _array_vals(): - if (dtypes == "numeric" and a.dtype in _numeric_dtypes - or dtypes == "integer_or_boolean" and a.dtype in _integer_or_boolean_dtypes - ): + if ( + dtypes == "numeric" + and a.dtype in _numeric_dtypes + or dtypes == "integer_or_boolean" + and a.dtype in _integer_or_boolean_dtypes + ): # Only test for no error getattr(a, op)() else: @@ -192,8 +210,8 @@ def _matmul_array_vals(): yield ones((4, 4), dtype=d) # Scalars always error - for _op in ['__matmul__', '__rmatmul__', '__imatmul__']: - for s in [1, 1., False]: + for _op in ["__matmul__", "__rmatmul__", "__imatmul__"]: + for s in [1, 1.0, False]: for a in _matmul_array_vals(): if (type(s) in [float, int] and a.dtype in _floating_dtypes or type(s) == int and a.dtype in _integer_dtypes): @@ -235,16 +253,17 @@ def _matmul_array_vals(): else: x.__imatmul__(y) + def test_python_scalar_construtors(): a = asarray(False) b = asarray(0) - c = asarray(0.) + c = asarray(0.0) assert bool(a) == bool(b) == bool(c) == False assert int(a) == int(b) == int(c) == 0 - assert float(a) == float(b) == float(c) == 0. + assert float(a) == float(b) == float(c) == 0.0 # bool/int/float should only be allowed on 0-D arrays. assert_raises(TypeError, lambda: bool(asarray([False]))) assert_raises(TypeError, lambda: int(asarray([0]))) - assert_raises(TypeError, lambda: float(asarray([0.]))) + assert_raises(TypeError, lambda: float(asarray([0.0]))) diff --git a/numpy/array_api/tests/test_creation_functions.py b/numpy/array_api/tests/test_creation_functions.py index 654f1d9b310..3cb8865cd1c 100644 --- a/numpy/array_api/tests/test_creation_functions.py +++ b/numpy/array_api/tests/test_creation_functions.py @@ -2,26 +2,53 @@ import numpy as np from .. import all -from .._creation_functions import (asarray, arange, empty, empty_like, eye, from_dlpack, full, full_like, linspace, meshgrid, ones, ones_like, zeros, zeros_like) +from .._creation_functions import ( + asarray, + arange, + empty, + empty_like, + eye, + from_dlpack, + full, + full_like, + linspace, + meshgrid, + ones, + ones_like, + zeros, + zeros_like, +) from .._array_object import Array -from .._dtypes import (_all_dtypes, _boolean_dtypes, _floating_dtypes, - _integer_dtypes, _integer_or_boolean_dtypes, - _numeric_dtypes, int8, int16, int32, int64, uint64) +from .._dtypes import ( + _all_dtypes, + _boolean_dtypes, + _floating_dtypes, + _integer_dtypes, + _integer_or_boolean_dtypes, + _numeric_dtypes, + int8, + int16, + int32, + int64, + uint64, +) + def test_asarray_errors(): # Test various protections against incorrect usage assert_raises(TypeError, lambda: Array([1])) - assert_raises(TypeError, lambda: asarray(['a'])) - assert_raises(ValueError, lambda: asarray([1.], dtype=np.float16)) + assert_raises(TypeError, lambda: asarray(["a"])) + assert_raises(ValueError, lambda: asarray([1.0], dtype=np.float16)) assert_raises(OverflowError, lambda: asarray(2**100)) # Preferably this would be OverflowError # assert_raises(OverflowError, lambda: asarray([2**100])) assert_raises(TypeError, lambda: asarray([2**100])) - asarray([1], device='cpu') # Doesn't error - assert_raises(ValueError, lambda: asarray([1], device='gpu')) + asarray([1], device="cpu") # Doesn't error + assert_raises(ValueError, lambda: asarray([1], device="gpu")) assert_raises(ValueError, lambda: asarray([1], dtype=int)) - assert_raises(ValueError, lambda: asarray([1], dtype='i')) + assert_raises(ValueError, lambda: asarray([1], dtype="i")) + def test_asarray_copy(): a = asarray([1]) @@ -36,68 +63,79 @@ def test_asarray_copy(): # assert all(b[0] == 0) assert_raises(NotImplementedError, lambda: asarray(a, copy=False)) + def test_arange_errors(): - arange(1, device='cpu') # Doesn't error - assert_raises(ValueError, lambda: arange(1, device='gpu')) + arange(1, device="cpu") # Doesn't error + assert_raises(ValueError, lambda: arange(1, device="gpu")) assert_raises(ValueError, lambda: arange(1, dtype=int)) - assert_raises(ValueError, lambda: arange(1, dtype='i')) + assert_raises(ValueError, lambda: arange(1, dtype="i")) + def test_empty_errors(): - empty((1,), device='cpu') # Doesn't error - assert_raises(ValueError, lambda: empty((1,), device='gpu')) + empty((1,), device="cpu") # Doesn't error + assert_raises(ValueError, lambda: empty((1,), device="gpu")) assert_raises(ValueError, lambda: empty((1,), dtype=int)) - assert_raises(ValueError, lambda: empty((1,), dtype='i')) + assert_raises(ValueError, lambda: empty((1,), dtype="i")) + def test_empty_like_errors(): - empty_like(asarray(1), device='cpu') # Doesn't error - assert_raises(ValueError, lambda: empty_like(asarray(1), device='gpu')) + empty_like(asarray(1), device="cpu") # Doesn't error + assert_raises(ValueError, lambda: empty_like(asarray(1), device="gpu")) assert_raises(ValueError, lambda: empty_like(asarray(1), dtype=int)) - assert_raises(ValueError, lambda: empty_like(asarray(1), dtype='i')) + assert_raises(ValueError, lambda: empty_like(asarray(1), dtype="i")) + def test_eye_errors(): - eye(1, device='cpu') # Doesn't error - assert_raises(ValueError, lambda: eye(1, device='gpu')) + eye(1, device="cpu") # Doesn't error + assert_raises(ValueError, lambda: eye(1, device="gpu")) assert_raises(ValueError, lambda: eye(1, dtype=int)) - assert_raises(ValueError, lambda: eye(1, dtype='i')) + assert_raises(ValueError, lambda: eye(1, dtype="i")) + def test_full_errors(): - full((1,), 0, device='cpu') # Doesn't error - assert_raises(ValueError, lambda: full((1,), 0, device='gpu')) + full((1,), 0, device="cpu") # Doesn't error + assert_raises(ValueError, lambda: full((1,), 0, device="gpu")) assert_raises(ValueError, lambda: full((1,), 0, dtype=int)) - assert_raises(ValueError, lambda: full((1,), 0, dtype='i')) + assert_raises(ValueError, lambda: full((1,), 0, dtype="i")) + def test_full_like_errors(): - full_like(asarray(1), 0, device='cpu') # Doesn't error - assert_raises(ValueError, lambda: full_like(asarray(1), 0, device='gpu')) + full_like(asarray(1), 0, device="cpu") # Doesn't error + assert_raises(ValueError, lambda: full_like(asarray(1), 0, device="gpu")) assert_raises(ValueError, lambda: full_like(asarray(1), 0, dtype=int)) - assert_raises(ValueError, lambda: full_like(asarray(1), 0, dtype='i')) + assert_raises(ValueError, lambda: full_like(asarray(1), 0, dtype="i")) + def test_linspace_errors(): - linspace(0, 1, 10, device='cpu') # Doesn't error - assert_raises(ValueError, lambda: linspace(0, 1, 10, device='gpu')) + linspace(0, 1, 10, device="cpu") # Doesn't error + assert_raises(ValueError, lambda: linspace(0, 1, 10, device="gpu")) assert_raises(ValueError, lambda: linspace(0, 1, 10, dtype=float)) - assert_raises(ValueError, lambda: linspace(0, 1, 10, dtype='f')) + assert_raises(ValueError, lambda: linspace(0, 1, 10, dtype="f")) + def test_ones_errors(): - ones((1,), device='cpu') # Doesn't error - assert_raises(ValueError, lambda: ones((1,), device='gpu')) + ones((1,), device="cpu") # Doesn't error + assert_raises(ValueError, lambda: ones((1,), device="gpu")) assert_raises(ValueError, lambda: ones((1,), dtype=int)) - assert_raises(ValueError, lambda: ones((1,), dtype='i')) + assert_raises(ValueError, lambda: ones((1,), dtype="i")) + def test_ones_like_errors(): - ones_like(asarray(1), device='cpu') # Doesn't error - assert_raises(ValueError, lambda: ones_like(asarray(1), device='gpu')) + ones_like(asarray(1), device="cpu") # Doesn't error + assert_raises(ValueError, lambda: ones_like(asarray(1), device="gpu")) assert_raises(ValueError, lambda: ones_like(asarray(1), dtype=int)) - assert_raises(ValueError, lambda: ones_like(asarray(1), dtype='i')) + assert_raises(ValueError, lambda: ones_like(asarray(1), dtype="i")) + def test_zeros_errors(): - zeros((1,), device='cpu') # Doesn't error - assert_raises(ValueError, lambda: zeros((1,), device='gpu')) + zeros((1,), device="cpu") # Doesn't error + assert_raises(ValueError, lambda: zeros((1,), device="gpu")) assert_raises(ValueError, lambda: zeros((1,), dtype=int)) - assert_raises(ValueError, lambda: zeros((1,), dtype='i')) + assert_raises(ValueError, lambda: zeros((1,), dtype="i")) + def test_zeros_like_errors(): - zeros_like(asarray(1), device='cpu') # Doesn't error - assert_raises(ValueError, lambda: zeros_like(asarray(1), device='gpu')) + zeros_like(asarray(1), device="cpu") # Doesn't error + assert_raises(ValueError, lambda: zeros_like(asarray(1), device="gpu")) assert_raises(ValueError, lambda: zeros_like(asarray(1), dtype=int)) - assert_raises(ValueError, lambda: zeros_like(asarray(1), dtype='i')) + assert_raises(ValueError, lambda: zeros_like(asarray(1), dtype="i")) diff --git a/numpy/array_api/tests/test_elementwise_functions.py b/numpy/array_api/tests/test_elementwise_functions.py index ec76cb7a7a7..a9274aec927 100644 --- a/numpy/array_api/tests/test_elementwise_functions.py +++ b/numpy/array_api/tests/test_elementwise_functions.py @@ -4,74 +4,80 @@ from .. import asarray, _elementwise_functions from .._elementwise_functions import bitwise_left_shift, bitwise_right_shift -from .._dtypes import (_dtype_categories, _boolean_dtypes, _floating_dtypes, - _integer_dtypes) +from .._dtypes import ( + _dtype_categories, + _boolean_dtypes, + _floating_dtypes, + _integer_dtypes, +) + def nargs(func): return len(getfullargspec(func).args) + def test_function_types(): # Test that every function accepts only the required input types. We only # test the negative cases here (error). The positive cases are tested in # the array API test suite. elementwise_function_input_types = { - 'abs': 'numeric', - 'acos': 'floating-point', - 'acosh': 'floating-point', - 'add': 'numeric', - 'asin': 'floating-point', - 'asinh': 'floating-point', - 'atan': 'floating-point', - 'atan2': 'floating-point', - 'atanh': 'floating-point', - 'bitwise_and': 'integer or boolean', - 'bitwise_invert': 'integer or boolean', - 'bitwise_left_shift': 'integer', - 'bitwise_or': 'integer or boolean', - 'bitwise_right_shift': 'integer', - 'bitwise_xor': 'integer or boolean', - 'ceil': 'numeric', - 'cos': 'floating-point', - 'cosh': 'floating-point', - 'divide': 'floating-point', - 'equal': 'all', - 'exp': 'floating-point', - 'expm1': 'floating-point', - 'floor': 'numeric', - 'floor_divide': 'numeric', - 'greater': 'numeric', - 'greater_equal': 'numeric', - 'isfinite': 'numeric', - 'isinf': 'numeric', - 'isnan': 'numeric', - 'less': 'numeric', - 'less_equal': 'numeric', - 'log': 'floating-point', - 'logaddexp': 'floating-point', - 'log10': 'floating-point', - 'log1p': 'floating-point', - 'log2': 'floating-point', - 'logical_and': 'boolean', - 'logical_not': 'boolean', - 'logical_or': 'boolean', - 'logical_xor': 'boolean', - 'multiply': 'numeric', - 'negative': 'numeric', - 'not_equal': 'all', - 'positive': 'numeric', - 'pow': 'floating-point', - 'remainder': 'numeric', - 'round': 'numeric', - 'sign': 'numeric', - 'sin': 'floating-point', - 'sinh': 'floating-point', - 'sqrt': 'floating-point', - 'square': 'numeric', - 'subtract': 'numeric', - 'tan': 'floating-point', - 'tanh': 'floating-point', - 'trunc': 'numeric', + "abs": "numeric", + "acos": "floating-point", + "acosh": "floating-point", + "add": "numeric", + "asin": "floating-point", + "asinh": "floating-point", + "atan": "floating-point", + "atan2": "floating-point", + "atanh": "floating-point", + "bitwise_and": "integer or boolean", + "bitwise_invert": "integer or boolean", + "bitwise_left_shift": "integer", + "bitwise_or": "integer or boolean", + "bitwise_right_shift": "integer", + "bitwise_xor": "integer or boolean", + "ceil": "numeric", + "cos": "floating-point", + "cosh": "floating-point", + "divide": "floating-point", + "equal": "all", + "exp": "floating-point", + "expm1": "floating-point", + "floor": "numeric", + "floor_divide": "numeric", + "greater": "numeric", + "greater_equal": "numeric", + "isfinite": "numeric", + "isinf": "numeric", + "isnan": "numeric", + "less": "numeric", + "less_equal": "numeric", + "log": "floating-point", + "logaddexp": "floating-point", + "log10": "floating-point", + "log1p": "floating-point", + "log2": "floating-point", + "logical_and": "boolean", + "logical_not": "boolean", + "logical_or": "boolean", + "logical_xor": "boolean", + "multiply": "numeric", + "negative": "numeric", + "not_equal": "all", + "positive": "numeric", + "pow": "floating-point", + "remainder": "numeric", + "round": "numeric", + "sign": "numeric", + "sin": "floating-point", + "sinh": "floating-point", + "sqrt": "floating-point", + "square": "numeric", + "subtract": "numeric", + "tan": "floating-point", + "tanh": "floating-point", + "trunc": "numeric", } def _array_vals(): @@ -80,7 +86,7 @@ def _array_vals(): for d in _boolean_dtypes: yield asarray(False, dtype=d) for d in _floating_dtypes: - yield asarray(1., dtype=d) + yield asarray(1.0, dtype=d) for x in _array_vals(): for func_name, types in elementwise_function_input_types.items(): @@ -94,7 +100,12 @@ def _array_vals(): if x.dtype not in dtypes: assert_raises(TypeError, lambda: func(x)) + def test_bitwise_shift_error(): # bitwise shift functions should raise when the second argument is negative - assert_raises(ValueError, lambda: bitwise_left_shift(asarray([1, 1]), asarray([1, -1]))) - assert_raises(ValueError, lambda: bitwise_right_shift(asarray([1, 1]), asarray([1, -1]))) + assert_raises( + ValueError, lambda: bitwise_left_shift(asarray([1, 1]), asarray([1, -1])) + ) + assert_raises( + ValueError, lambda: bitwise_right_shift(asarray([1, 1]), asarray([1, -1])) + ) From d5956c170b07cf26b05c921d810dc387d7e819da Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 12 Aug 2021 15:22:58 -0600 Subject: [PATCH 145/151] Fix the return annotation for numpy.array_api.Array.__setitem__ --- numpy/array_api/_array_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/array_api/_array_object.py b/numpy/array_api/_array_object.py index 0f511a577b6..2d746e78beb 100644 --- a/numpy/array_api/_array_object.py +++ b/numpy/array_api/_array_object.py @@ -647,7 +647,7 @@ def __setitem__( ], value: Union[int, float, bool, Array], /, - ) -> Array: + ) -> None: """ Performs the operation __setitem__. """ From 90537b5dac1d0c569baa794967b919ae4f6fdcca Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 12 Aug 2021 15:34:45 -0600 Subject: [PATCH 146/151] Add smallest_normal to the array API finfo This was blocked on #18536, which has been merged. --- numpy/array_api/_data_type_functions.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/numpy/array_api/_data_type_functions.py b/numpy/array_api/_data_type_functions.py index e6121a8a4e4..fd92aa2502c 100644 --- a/numpy/array_api/_data_type_functions.py +++ b/numpy/array_api/_data_type_functions.py @@ -60,10 +60,7 @@ class finfo_object: eps: float max: float min: float - # Note: smallest_normal is part of the array API spec, but cannot be used - # until https://github.com/numpy/numpy/pull/18536 is merged. - - # smallest_normal: float + smallest_normal: float @dataclass @@ -87,8 +84,7 @@ def finfo(type: Union[Dtype, Array], /) -> finfo_object: float(fi.eps), float(fi.max), float(fi.min), - # TODO: Uncomment this when #18536 is merged. - # float(fi.smallest_normal), + float(fi.smallest_normal), ) From 22cb4f3156218abe3c20adcfaaff4fc609cc8a72 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Thu, 12 Aug 2021 16:56:10 -0600 Subject: [PATCH 147/151] Make sure array_api is included in the public API tests --- numpy/tests/test_public_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/numpy/tests/test_public_api.py b/numpy/tests/test_public_api.py index 6e4a8dee0a7..59e7b066c22 100644 --- a/numpy/tests/test_public_api.py +++ b/numpy/tests/test_public_api.py @@ -137,6 +137,7 @@ def test_NPY_NO_EXPORT(): # current status is fine. For others it may make sense to work on making them # private, to clean up our public API and avoid confusion. PUBLIC_MODULES = ['numpy.' + s for s in [ + "array_api", "ctypeslib", "distutils", "distutils.cpuinfo", From 9978cc5a1861533abf7c6ed7b0f4e1d732171094 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 23 Aug 2021 15:21:24 -0600 Subject: [PATCH 148/151] Remove Python 3.7 checks from the array API code NumPy has dropped Python 3.7, so these are no longer necessary (and they didn't completely work anyway). --- numpy/_pytesttester.py | 18 ++++++------------ numpy/array_api/__init__.py | 5 ----- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/numpy/_pytesttester.py b/numpy/_pytesttester.py index 1e24f75a7b6..bfcbd4f1f60 100644 --- a/numpy/_pytesttester.py +++ b/numpy/_pytesttester.py @@ -144,18 +144,12 @@ def __call__(self, label='fast', verbose=1, extra_argv=None, # so fetch module for suppression here. from numpy.distutils import cpuinfo - if sys.version_info >= (3, 8): - # Ignore the warning from importing the array_api submodule. This - # warning is done on import, so it would break pytest collection, - # but importing it early here prevents the warning from being - # issued when it imported again. - warnings.simplefilter("ignore") - import numpy.array_api - else: - # The array_api submodule is Python 3.8+ only due to the use - # of positional-only argument syntax. We have to ignore it - # completely or the tests will fail at the collection stage. - pytest_args += ['--ignore-glob=numpy/array_api/*'] + # Ignore the warning from importing the array_api submodule. This + # warning is done on import, so it would break pytest collection, + # but importing it early here prevents the warning from being + # issued when it imported again. + warnings.simplefilter("ignore") + import numpy.array_api # Filter out annoying import messages. Want these in both develop and # release mode. diff --git a/numpy/array_api/__init__.py b/numpy/array_api/__init__.py index 53c1f385047..1e1ff242fca 100644 --- a/numpy/array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -120,11 +120,6 @@ import sys -# numpy.array_api is 3.8+ because it makes extensive use of positional-only -# arguments. -if sys.version_info < (3, 8): - raise ImportError("The numpy.array_api submodule requires Python 3.8 or greater.") - import warnings warnings.warn( From f12fa6ff33a0c5b5c94b839f26f25d2aebc26aed Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 23 Aug 2021 15:38:59 -0600 Subject: [PATCH 149/151] Add a release note entry for #18585 --- .../upcoming_changes/18585.new_feature.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/release/upcoming_changes/18585.new_feature.rst diff --git a/doc/release/upcoming_changes/18585.new_feature.rst b/doc/release/upcoming_changes/18585.new_feature.rst new file mode 100644 index 00000000000..bb83d755cf4 --- /dev/null +++ b/doc/release/upcoming_changes/18585.new_feature.rst @@ -0,0 +1,15 @@ +Implementation of the NEP 47 (adopting the array API standard) +-------------------------------------------------------------- + +An initial implementation of `NEP 47`_ (adoption the array API standard) has +been added as ``numpy.array_api``. The implementation is experimental and will +issue a UserWarning on import, as the `array API standard +`_ is still in draft state. +``numpy.array_api`` is a conforming implementation of the array API standard, +which is also minimal, meaning that only those functions and behaviors that +are required by the standard are implemented (see the NEP for more info). +Libraries wishing to make use of the array API standard are encouraged to use +``numpy.array_api`` to check that they are only using functionality that is +guaranteed to be present in standard conforming implementations. + +.. _`NEP 47`: https://numpy.org/neps/nep-0047-array-api-standard.html From 06ec0ec8dadf9e0e9f7518c9817b95f14df9d7be Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 23 Aug 2021 17:46:13 -0600 Subject: [PATCH 150/151] Remove an unused import --- numpy/array_api/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/numpy/array_api/__init__.py b/numpy/array_api/__init__.py index 1e1ff242fca..79015750497 100644 --- a/numpy/array_api/__init__.py +++ b/numpy/array_api/__init__.py @@ -118,8 +118,6 @@ """ -import sys - import warnings warnings.warn( From 7091e4c48ce7af8a5263b6808a6d7976d4af4c6f Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Mon, 23 Aug 2021 17:46:26 -0600 Subject: [PATCH 151/151] Use catch_warnings(record=True) instead of simplefilter('ignore') There is a test that fails in the presence of simplefilter('ignore') (test_warnings.py). catch_warnings(record=True) seems to be a way to get the same behavior without failing the test. --- numpy/_pytesttester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_pytesttester.py b/numpy/_pytesttester.py index bfcbd4f1f60..8decb9dd79a 100644 --- a/numpy/_pytesttester.py +++ b/numpy/_pytesttester.py @@ -144,11 +144,11 @@ def __call__(self, label='fast', verbose=1, extra_argv=None, # so fetch module for suppression here. from numpy.distutils import cpuinfo + with warnings.catch_warnings(record=True): # Ignore the warning from importing the array_api submodule. This # warning is done on import, so it would break pytest collection, # but importing it early here prevents the warning from being # issued when it imported again. - warnings.simplefilter("ignore") import numpy.array_api # Filter out annoying import messages. Want these in both develop and