From 781d8551a062e85a5097f4cde3e593736c82174e Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Thu, 20 Sep 2018 19:02:01 -0700 Subject: [PATCH 01/12] ENH: initial implementation of core __array_function__ machinery I'd like to implement NEP-18 in a multi-step process: 1. (This PR) Pure Python implementation of `__array_function__` machinery, based on the prototype implementation from NEP-18. 2. Rewrite this machinery in C as needed to improve performance. 3. Implement overrides on NumPy functions. Steps 2 and 3 should be able to happen in parallel (and with other people contributing!). This PR still needs more tests, especially for `ndarray.__array_function__` --- numpy/core/_methods.py | 12 ++ numpy/core/overrides.py | 75 +++++++++++ numpy/core/src/multiarray/methods.c | 10 ++ numpy/core/tests/test_overrides.py | 199 ++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 numpy/core/overrides.py create mode 100644 numpy/core/tests/test_overrides.py diff --git a/numpy/core/_methods.py b/numpy/core/_methods.py index 33f6d01a89c3..f49f64febfad 100644 --- a/numpy/core/_methods.py +++ b/numpy/core/_methods.py @@ -154,3 +154,15 @@ def _ptp(a, axis=None, out=None, keepdims=False): umr_minimum(a, axis, None, None, keepdims), out ) + +def _array_function(self, func, types, args, kwargs): + # TODO: rewrite this in C + # Cannot handle items that have __array_function__ other than our own. + for t in types: + if (hasattr(t, '__array_function__') and + t.__array_function__ is not mu.ndarray.__array_function__): + return NotImplemented + + # Arguments contain no overrides, so we can safely call the + # overloaded function again. + return func(*args, **kwargs) diff --git a/numpy/core/overrides.py b/numpy/core/overrides.py new file mode 100644 index 000000000000..8f7676f66983 --- /dev/null +++ b/numpy/core/overrides.py @@ -0,0 +1,75 @@ +"""Preliminary implementation of NEP-18 + +TODO: rewrite this in C for performance. +""" +import functools +from numpy.core.multiarray import ndarray + + +def get_overloaded_types_and_args(relevant_args): + """Returns a list of arguments on which to call __array_function__. + + __array_function__ implementations should be called in order on the return + values from this function. + """ + # Runtime is O(num_arguments * num_unique_types) + overloaded_types = [] + overloaded_args = [] + for arg in relevant_args: + arg_type = type(arg) + if arg_type not in overloaded_types: + try: + array_function = arg_type.__array_function__ + except AttributeError: + continue + + overloaded_types.append(arg_type) + + if array_function is not ndarray.__array_function__: + index = len(overloaded_args) + for i, old_arg in enumerate(overloaded_args): + if issubclass(arg_type, type(old_arg)): + index = i + break + overloaded_args.insert(index, arg) + + return tuple(overloaded_types), tuple(overloaded_args) + + +def try_array_function_override(func, relevant_arguments, args, kwargs): + # TODO: consider simplifying the interface, to only require either `types` + # (by calling __array_function__ a classmethod) or `overloaded_args` (by + # dropping `types` from the signature of __array_function__) + types, overloaded_args = get_overloaded_types_and_args(relevant_arguments) + if not overloaded_args: + return False, None + + for overloaded_arg in overloaded_args: + # Note that we're only calling __array_function__ on the *first* + # occurence of each argument type. This is necessary for reasonable + # performance with a possibly long list of overloaded arguments, for + # which each __array_function__ implementation might reasonably need to + # check all argument types. + result = overloaded_arg.__array_function__(func, types, args, kwargs) + + if result is not NotImplemented: + return True, result + + raise TypeError('no implementation found for {} on types that implement ' + '__array_function__: {}' + .format(func, list(map(type, overloaded_args)))) + + +def array_function_dispatch(dispatcher): + """Wrap a function for dispatch with the __array_function__ protocol.""" + def decorator(func): + @functools.wraps(func) + def new_func(*args, **kwargs): + relevant_arguments = dispatcher(*args, **kwargs) + success, value = try_array_function_override( + new_func, relevant_arguments, args, kwargs) + if success: + return value + return func(*args, **kwargs) + return new_func + return decorator diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 3d2cce5e18f5..6317d6a16a5d 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -1021,6 +1021,13 @@ array_ufunc(PyArrayObject *self, PyObject *args, PyObject *kwds) } +static PyObject * +array_function(PyArrayObject *self, PyObject *args, PyObject *kwds) +{ + NPY_FORWARD_NDARRAY_METHOD("_array_function"); +} + + static PyObject * array_copy(PyArrayObject *self, PyObject *args, PyObject *kwds) { @@ -2472,6 +2479,9 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { {"__array_ufunc__", (PyCFunction)array_ufunc, METH_VARARGS | METH_KEYWORDS, NULL}, + {"__array_function__", + (PyCFunction)array_function, + METH_VARARGS | METH_KEYWORDS, NULL}, #ifndef NPY_PY3K {"__unicode__", diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py new file mode 100644 index 000000000000..4c660b85eea8 --- /dev/null +++ b/numpy/core/tests/test_overrides.py @@ -0,0 +1,199 @@ +from __future__ import division, absolute_import, print_function + +import pickle + +import numpy as np +from numpy.testing import ( + assert_, assert_equal, assert_raises, assert_raises_regex) +from numpy.core.overrides import ( + get_overloaded_types_and_args, array_function_dispatch) + + +def _get_overloaded_args(relevant_args): + types, args = get_overloaded_types_and_args(relevant_args) + return args + + +_IMPLEMENTED = None + + +class TestGetOverloadedTypesAndArgs(object): + + def test_ndarray(self): + array = np.array(1) + + types, args = get_overloaded_types_and_args([array]) + assert_equal(set(types), {np.ndarray}) + assert_equal(list(args), []) + + types, args = get_overloaded_types_and_args([array, array]) + assert_equal(len(types), 1) + assert_equal(set(types), {np.ndarray}) + assert_equal(list(args), []) + + types, args = get_overloaded_types_and_args([array, 1]) + assert_equal(set(types), {np.ndarray}) + assert_equal(list(args), []) + + types, args = get_overloaded_types_and_args([1, array]) + assert_equal(set(types), {np.ndarray}) + assert_equal(list(args), []) + + def test_ndarray_subclasses(self): + + class OverrideSub(np.ndarray): + __array_function__ = _IMPLEMENTED + + class NoOverrideSub(np.ndarray): + pass + + array = np.array(1).view(np.ndarray) + override_sub = np.array(1).view(OverrideSub) + no_override_sub = np.array(1).view(NoOverrideSub) + + types, args = get_overloaded_types_and_args([array, override_sub]) + assert_equal(set(types), {np.ndarray, OverrideSub}) + assert_equal(list(args), [override_sub]) + + types, args = get_overloaded_types_and_args([array, no_override_sub]) + assert_equal(set(types), {np.ndarray, NoOverrideSub}) + assert_equal(list(args), []) + + types, args = get_overloaded_types_and_args( + [override_sub, no_override_sub]) + assert_equal(set(types), {OverrideSub, NoOverrideSub}) + assert_equal(list(args), [override_sub]) + + def test_ndarray_and_duck_array(self): + + class Other(object): + __array_function__ = _IMPLEMENTED + + array = np.array(1) + other = Other() + + types, args = get_overloaded_types_and_args([other, array]) + assert_equal(set(types), {np.ndarray, Other}) + assert_equal(list(args), [other]) + + types, args = get_overloaded_types_and_args([array, other]) + assert_equal(set(types), {np.ndarray, Other}) + assert_equal(list(args), [other]) + + def test_many_duck_arrays(self): + + class A(object): + __array_function__ = _IMPLEMENTED + + class B(A): + __array_function__ = _IMPLEMENTED + + class C(A): + __array_function__ = _IMPLEMENTED + + class D(object): + __array_function__ = _IMPLEMENTED + + a = A() + b = B() + c = C() + d = D() + + assert_equal(_get_overloaded_args([1]), []) + assert_equal(_get_overloaded_args([a]), [a]) + assert_equal(_get_overloaded_args([a, 1]), [a]) + assert_equal(_get_overloaded_args([a, a, a]), [a]) + assert_equal(_get_overloaded_args([a, d, a]), [a, d]) + assert_equal(_get_overloaded_args([a, b]), [b, a]) + assert_equal(_get_overloaded_args([b, a]), [b, a]) + assert_equal(_get_overloaded_args([a, b, c]), [b, c, a]) + assert_equal(_get_overloaded_args([a, c, b]), [c, b, a]) + + +# need to define this at the top level to test pickling +@array_function_dispatch(lambda array: (array,)) +def dispatched_one_arg(array): + """Docstring.""" + return 'original' + + +class TestArrayFunctionDispatch(object): + + def test_pickle(self): + roundtripped = pickle.loads(pickle.dumps(dispatched_one_arg)) + assert_(roundtripped is dispatched_one_arg) + + def test_docstring(self): + assert_equal(dispatched_one_arg.__doc__, 'Docstring.') + + +def _new_duck_type_and_implements(): + HANDLED_FUNCTIONS = {} + + class MyArray(object): + def __array_function__(self, func, types, args, kwargs): + if func not in HANDLED_FUNCTIONS: + return NotImplemented + if not all(issubclass(t, MyArray) for t in types): + return NotImplemented + return HANDLED_FUNCTIONS[func](*args, **kwargs) + + def implements(numpy_function): + """Register an __array_function__ implementations.""" + def decorator(func): + HANDLED_FUNCTIONS[numpy_function] = func + return func + return decorator + + return (MyArray, implements) + + +class TestArrayFunctionImplementation(object): + + def test_one_arg(self): + MyArray, implements = _new_duck_type_and_implements() + + @implements(dispatched_one_arg) + def _(array): + return 'myarray' + + assert_equal(dispatched_one_arg(1), 'original') + assert_equal(dispatched_one_arg(MyArray()), 'myarray') + + def test_optional_args(self): + MyArray, implements = _new_duck_type_and_implements() + + @array_function_dispatch(lambda array, option=None: (array,)) + def func_with_option(array, option='default'): + return option + + @implements(func_with_option) + def my_array_func_with_option(array, new_option='myarray'): + return new_option + + # we don't need to implement every option on __array_function__ + # implementations + assert_equal(func_with_option(1), 'default') + assert_equal(func_with_option(1, option='extra'), 'extra') + assert_equal(func_with_option(MyArray()), 'myarray') + with assert_raises(TypeError): + func_with_option(MyArray(), option='extra') + + # but new options on implementations can't be used + result = my_array_func_with_option(MyArray(), new_option='yes') + assert_equal(result, 'yes') + with assert_raises(TypeError): + func_with_option(MyArray(), new_option='no') + + def test_unimplemented(self): + MyArray, implements = _new_duck_type_and_implements() + + @array_function_dispatch(lambda array: (array,)) + def func(array): + return array + + array = np.array(1) + assert_(func(array) is array) + + with assert_raises_regex(TypeError, 'no implementation found'): + func(MyArray()) From 4ece3b1ce10e5aca966d8adfb85e6761747567b9 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 21 Sep 2018 08:25:07 -0700 Subject: [PATCH 02/12] Add test for ndarray.__array_function__ --- numpy/core/tests/test_overrides.py | 44 +++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 4c660b85eea8..74288c47c06b 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -14,7 +14,8 @@ def _get_overloaded_args(relevant_args): return args -_IMPLEMENTED = None +def _return_self(self, *args, **kwargs): + return self class TestGetOverloadedTypesAndArgs(object): @@ -42,7 +43,7 @@ def test_ndarray(self): def test_ndarray_subclasses(self): class OverrideSub(np.ndarray): - __array_function__ = _IMPLEMENTED + __array_function__ = _return_self class NoOverrideSub(np.ndarray): pass @@ -67,7 +68,7 @@ class NoOverrideSub(np.ndarray): def test_ndarray_and_duck_array(self): class Other(object): - __array_function__ = _IMPLEMENTED + __array_function__ = _return_self array = np.array(1) other = Other() @@ -83,16 +84,16 @@ class Other(object): def test_many_duck_arrays(self): class A(object): - __array_function__ = _IMPLEMENTED + __array_function__ = _return_self class B(A): - __array_function__ = _IMPLEMENTED + __array_function__ = _return_self class C(A): - __array_function__ = _IMPLEMENTED + __array_function__ = _return_self class D(object): - __array_function__ = _IMPLEMENTED + __array_function__ = _return_self a = A() b = B() @@ -110,6 +111,34 @@ class D(object): assert_equal(_get_overloaded_args([a, c, b]), [c, b, a]) +class TestNDArrayArrayFunction(object): + + def test_method(self): + + class SubOverride(np.ndarray): + __array_function__ = _return_self + + class NoOverrideSub(np.ndarray): + pass + + array = np.array(1) + + def func(): + return 'original' + + result = array.__array_function__( + func=func, types=(np.ndarray,), args=(), kwargs={}) + assert_equal(result, 'original') + + result = array.__array_function__( + func=func, types=(np.ndarray, SubOverride), args=(), kwargs={}) + assert_(result is NotImplemented) + + result = array.__array_function__( + func=func, types=(np.ndarray, NoOverrideSub), args=(), kwargs={}) + assert_equal(result, 'original') + + # need to define this at the top level to test pickling @array_function_dispatch(lambda array: (array,)) def dispatched_one_arg(array): @@ -128,6 +157,7 @@ def test_docstring(self): def _new_duck_type_and_implements(): + """Create a duck array type and implements functions.""" HANDLED_FUNCTIONS = {} class MyArray(object): From a05d1c2450074577f01eabe2e7e3a5ca8e64c847 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 21 Sep 2018 08:28:52 -0700 Subject: [PATCH 03/12] Add an interface test --- numpy/core/tests/test_overrides.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 74288c47c06b..72954525a1d2 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -155,6 +155,20 @@ def test_pickle(self): def test_docstring(self): assert_equal(dispatched_one_arg.__doc__, 'Docstring.') + def test_interface(self): + + class MyArray(object): + def __array_function__(self, func, types, args, kwargs): + return (self, func, types, args, kwargs) + + original = MyArray() + (obj, func, types, args, kwargs) = dispatched_one_arg(original) + assert_(obj is original) + assert_(func is dispatched_one_arg) + assert_equal(set(types), {MyArray}) + assert_equal(args, (original,)) + assert_equal(kwargs, {}) + def _new_duck_type_and_implements(): """Create a duck array type and implements functions.""" From ea1350107f9b2c7fe6a62da5e7090c5a43c34b8a Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 21 Sep 2018 11:11:11 -0700 Subject: [PATCH 04/12] Fix test failures checking __doc__ attribute --- numpy/core/tests/test_overrides.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 72954525a1d2..4f69c8cc1539 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -1,6 +1,7 @@ from __future__ import division, absolute_import, print_function import pickle +import sys import numpy as np from numpy.testing import ( @@ -152,8 +153,10 @@ def test_pickle(self): roundtripped = pickle.loads(pickle.dumps(dispatched_one_arg)) assert_(roundtripped is dispatched_one_arg) - def test_docstring(self): - assert_equal(dispatched_one_arg.__doc__, 'Docstring.') + def test_name_and_docstring(self): + assert_equal(dispatched_one_arg.__name__, 'dispatched_one_arg') + if sys.flags.optimize < 2: + assert_equal(dispatched_one_arg.__doc__, 'Docstring.') def test_interface(self): From 415f327069d26b7ff6159ef1ebacd1fdeaaac6a5 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 21 Sep 2018 18:15:09 -0700 Subject: [PATCH 05/12] Add initial benchmarks for __array_function__ --- benchmarks/benchmarks/bench_overrides.py | 61 ++++++++++++++++++++++++ benchmarks/benchmarks/common.py | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 benchmarks/benchmarks/bench_overrides.py diff --git a/benchmarks/benchmarks/bench_overrides.py b/benchmarks/benchmarks/bench_overrides.py new file mode 100644 index 000000000000..2cb94c95ce60 --- /dev/null +++ b/benchmarks/benchmarks/bench_overrides.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import, division, print_function + +from .common import Benchmark + +from numpy.core.overrides import array_function_dispatch +import numpy as np + + +def _broadcast_to_dispatcher(array, shape, subok=None): + return (array,) + + +@array_function_dispatch(_broadcast_to_dispatcher) +def mock_broadcast_to(array, shape, subok=False): + pass + + +def _concatenate_dispatcher(arrays, axis=None, out=None): + for array in arrays: + yield array + if out is not None: + yield out + + +@array_function_dispatch(_concatenate_dispatcher) +def mock_concatenate(arrays, axis=0, out=None): + pass + + +class DuckArray(object): + def __array_function__(self, func, types, args, kwargs): + pass + + +class ArrayFunction(Benchmark): + + def setup(self): + self.numpy_array = np.array(1) + self.numpy_arrays = [np.array(1), np.array(2)] + self.many_arrays = 500 * self.numpy_arrays + self.duck_array = DuckArray() + self.duck_arrays = [DuckArray(), DuckArray()] + self.mixed_arrays = [np.array(1), DuckArray()] + + def time_mock_broadcast_to_numpy(self): + mock_broadcast_to(self.numpy_array, ()) + + def time_mock_broadcast_to_duck(self): + mock_broadcast_to(self.duck_array, ()) + + def time_mock_concatenate_numpy(self): + mock_concatenate(self.numpy_arrays, axis=0) + + def time_mock_concatenate_many(self): + mock_concatenate(self.many_arrays, axis=0) + + def time_mock_concatenate_duck(self): + mock_concatenate(self.duck_arrays, axis=0) + + def time_mock_concatenate_mixed(self): + mock_concatenate(self.mixed_arrays, axis=0) diff --git a/benchmarks/benchmarks/common.py b/benchmarks/benchmarks/common.py index 18a09fd40551..d720eaaa89b8 100644 --- a/benchmarks/benchmarks/common.py +++ b/benchmarks/benchmarks/common.py @@ -113,4 +113,4 @@ def get_indexes_rand_(): class Benchmark(object): - goal_time = 0.25 + sample_time = 0.25 From 234ca8957f444cea408e093a01bbb43108b6c4c7 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sat, 22 Sep 2018 16:37:54 -0700 Subject: [PATCH 06/12] Add another test for unimplemented __array_function__ --- numpy/core/tests/test_overrides.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 4f69c8cc1539..1afb0a8f1033 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -172,6 +172,16 @@ def __array_function__(self, func, types, args, kwargs): assert_equal(args, (original,)) assert_equal(kwargs, {}) + def test_not_implemented(self): + + class MyArray(object): + def __array_function__(self, func, types, args, kwargs): + return NotImplemented + + array = MyArray() + with assert_raises_regex(TypeError, 'no implementation found'): + dispatched_one_arg(array) + def _new_duck_type_and_implements(): """Create a duck array type and implements functions.""" @@ -232,7 +242,7 @@ def my_array_func_with_option(array, new_option='myarray'): with assert_raises(TypeError): func_with_option(MyArray(), new_option='no') - def test_unimplemented(self): + def test_not_implemented(self): MyArray, implements = _new_duck_type_and_implements() @array_function_dispatch(lambda array: (array,)) From ac2e2f3cb3c2f6fb8424abc48a9509d32011360d Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sun, 23 Sep 2018 20:59:11 -0700 Subject: [PATCH 07/12] Add comment on subclasses ordering --- numpy/core/overrides.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/numpy/core/overrides.py b/numpy/core/overrides.py index 8f7676f66983..06e00510246c 100644 --- a/numpy/core/overrides.py +++ b/numpy/core/overrides.py @@ -26,6 +26,9 @@ def get_overloaded_types_and_args(relevant_args): overloaded_types.append(arg_type) if array_function is not ndarray.__array_function__: + # By default, insert this argument at the end, but if it is + # subclass of another argument, insert it before that argument. + # This ensures "subclasses before superclasses". index = len(overloaded_args) for i, old_arg in enumerate(overloaded_args): if issubclass(arg_type, type(old_arg)): From 0da1b95ea9180ab613eccc80ebe39eb4f48d99d3 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Sun, 23 Sep 2018 21:19:40 -0700 Subject: [PATCH 08/12] Fix precedence of ndarray subclasses and misc cleanup --- numpy/core/overrides.py | 66 +++++++++++++++++------------- numpy/core/tests/test_overrides.py | 17 ++++++++ 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/numpy/core/overrides.py b/numpy/core/overrides.py index 06e00510246c..c1d5e38643aa 100644 --- a/numpy/core/overrides.py +++ b/numpy/core/overrides.py @@ -6,6 +6,9 @@ from numpy.core.multiarray import ndarray +_NDARRAY_ARRAY_FUNCTION = ndarray.__array_function__ + + def get_overloaded_types_and_args(relevant_args): """Returns a list of arguments on which to call __array_function__. @@ -17,36 +20,32 @@ def get_overloaded_types_and_args(relevant_args): overloaded_args = [] for arg in relevant_args: arg_type = type(arg) - if arg_type not in overloaded_types: - try: - array_function = arg_type.__array_function__ - except AttributeError: - continue + if (arg_type not in overloaded_types and + hasattr(arg_type, '__array_function__')): overloaded_types.append(arg_type) - if array_function is not ndarray.__array_function__: - # By default, insert this argument at the end, but if it is - # subclass of another argument, insert it before that argument. - # This ensures "subclasses before superclasses". - index = len(overloaded_args) - for i, old_arg in enumerate(overloaded_args): - if issubclass(arg_type, type(old_arg)): - index = i - break - overloaded_args.insert(index, arg) + # By default, insert this argument at the end, but if it is + # subclass of another argument, insert it before that argument. + # This ensures "subclasses before superclasses". + index = len(overloaded_args) + for i, old_arg in enumerate(overloaded_args): + if issubclass(arg_type, type(old_arg)): + index = i + break + overloaded_args.insert(index, arg) - return tuple(overloaded_types), tuple(overloaded_args) + # Special handling for ndarray. + overloaded_args = [ + arg for arg in overloaded_args + if type(arg).__array_function__ is not _NDARRAY_ARRAY_FUNCTION + ] + return overloaded_types, overloaded_args -def try_array_function_override(func, relevant_arguments, args, kwargs): - # TODO: consider simplifying the interface, to only require either `types` - # (by calling __array_function__ a classmethod) or `overloaded_args` (by - # dropping `types` from the signature of __array_function__) - types, overloaded_args = get_overloaded_types_and_args(relevant_arguments) - if not overloaded_args: - return False, None +def array_function_override(overloaded_args, func, types, args, kwargs): + """Call __array_function__ implementations.""" for overloaded_arg in overloaded_args: # Note that we're only calling __array_function__ on the *first* # occurence of each argument type. This is necessary for reasonable @@ -56,7 +55,7 @@ def try_array_function_override(func, relevant_arguments, args, kwargs): result = overloaded_arg.__array_function__(func, types, args, kwargs) if result is not NotImplemented: - return True, result + return result raise TypeError('no implementation found for {} on types that implement ' '__array_function__: {}' @@ -68,11 +67,20 @@ def array_function_dispatch(dispatcher): def decorator(func): @functools.wraps(func) def new_func(*args, **kwargs): + # Collect array-like arguments. relevant_arguments = dispatcher(*args, **kwargs) - success, value = try_array_function_override( - new_func, relevant_arguments, args, kwargs) - if success: - return value - return func(*args, **kwargs) + # Check for __array_function__ methods. + types, overloaded_args = get_overloaded_types_and_args( + relevant_arguments) + # Call overrides, if necessary. + if overloaded_args: + # new_func is the function exposed in NumPy's public API. We + # use it instead of func so __array_function__ implementations + # can do equality/identity comparisons. + return array_function_override( + overloaded_args, new_func, types, args, kwargs) + else: + return func(*args, **kwargs) + return new_func return decorator diff --git a/numpy/core/tests/test_overrides.py b/numpy/core/tests/test_overrides.py index 1afb0a8f1033..7f6157a5bf09 100644 --- a/numpy/core/tests/test_overrides.py +++ b/numpy/core/tests/test_overrides.py @@ -82,6 +82,23 @@ class Other(object): assert_equal(set(types), {np.ndarray, Other}) assert_equal(list(args), [other]) + def test_ndarray_subclass_and_duck_array(self): + + class OverrideSub(np.ndarray): + __array_function__ = _return_self + + class Other(object): + __array_function__ = _return_self + + array = np.array(1) + subarray = np.array(1).view(OverrideSub) + other = Other() + + assert_equal(_get_overloaded_args([array, subarray, other]), + [subarray, other]) + assert_equal(_get_overloaded_args([array, other, subarray]), + [subarray, other]) + def test_many_duck_arrays(self): class A(object): From 1846ac335da808cb8bf6f9b1950933348a40d200 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Mon, 24 Sep 2018 08:46:49 -0700 Subject: [PATCH 09/12] CLN: remove the internal Benchmark class --- benchmarks/benchmarks/bench_app.py | 6 ++---- benchmarks/benchmarks/bench_core.py | 16 +++++++------- benchmarks/benchmarks/bench_function_base.py | 18 +++++++--------- benchmarks/benchmarks/bench_indexing.py | 8 +++---- benchmarks/benchmarks/bench_io.py | 22 ++++++++++---------- benchmarks/benchmarks/bench_lib.py | 8 +++---- benchmarks/benchmarks/bench_linalg.py | 8 +++---- benchmarks/benchmarks/bench_ma.py | 10 ++++----- benchmarks/benchmarks/bench_overrides.py | 4 +--- benchmarks/benchmarks/bench_random.py | 12 +++++------ benchmarks/benchmarks/bench_reduce.py | 12 +++++------ benchmarks/benchmarks/bench_shape_base.py | 6 ++---- benchmarks/benchmarks/bench_ufunc.py | 18 ++++++++-------- benchmarks/benchmarks/common.py | 4 ---- 14 files changed, 66 insertions(+), 86 deletions(-) diff --git a/benchmarks/benchmarks/bench_app.py b/benchmarks/benchmarks/bench_app.py index ccf6e4c4af85..bc217c3ec03d 100644 --- a/benchmarks/benchmarks/bench_app.py +++ b/benchmarks/benchmarks/bench_app.py @@ -1,13 +1,11 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np from six.moves import xrange -class LaplaceInplace(Benchmark): +class LaplaceInplace(object): params = ['inplace', 'normal'] param_names = ['update'] @@ -53,7 +51,7 @@ def time_it(self, update): self.run() -class MaxesOfDots(Benchmark): +class MaxesOfDots(object): def setup(self): np.random.seed(1) nsubj = 5 diff --git a/benchmarks/benchmarks/bench_core.py b/benchmarks/benchmarks/bench_core.py index 26cffcab1192..b6cbd93508fa 100644 --- a/benchmarks/benchmarks/bench_core.py +++ b/benchmarks/benchmarks/bench_core.py @@ -1,11 +1,9 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np -class Core(Benchmark): +class Core(object): def setup(self): self.l100 = range(100) self.l50 = range(50) @@ -76,7 +74,7 @@ def time_tril_l10x10(self): np.tril(self.l10x10) -class Temporaries(Benchmark): +class Temporaries(object): def setup(self): self.amid = np.ones(50000) self.bmid = np.ones(50000) @@ -96,7 +94,7 @@ def time_large2(self): (self.alarge + self.blarge) - 2 -class CorrConv(Benchmark): +class CorrConv(object): params = [[50, 1000, 1e5], [10, 100, 1000, 1e4], ['valid', 'same', 'full']] @@ -113,7 +111,7 @@ def time_convolve(self, size1, size2, mode): np.convolve(self.x1, self.x2, mode=mode) -class CountNonzero(Benchmark): +class CountNonzero(object): param_names = ['numaxes', 'size', 'dtype'] params = [ [1, 2, 3], @@ -137,7 +135,7 @@ def time_count_nonzero_multi_axis(self, numaxes, size, dtype): self.x.ndim - 1, self.x.ndim - 2)) -class PackBits(Benchmark): +class PackBits(object): param_names = ['dtype'] params = [[bool, np.uintp]] def setup(self, dtype): @@ -154,7 +152,7 @@ def time_packbits_axis1(self, dtype): np.packbits(self.d2, axis=1) -class UnpackBits(Benchmark): +class UnpackBits(object): def setup(self): self.d = np.ones(10000, dtype=np.uint8) self.d2 = np.ones((200, 1000), dtype=np.uint8) @@ -169,6 +167,6 @@ def time_unpackbits_axis1(self): np.unpackbits(self.d2, axis=1) -class Indices(Benchmark): +class Indices(object): def time_indices(self): np.indices((1000, 500)) diff --git a/benchmarks/benchmarks/bench_function_base.py b/benchmarks/benchmarks/bench_function_base.py index a45525793b14..eea1085288c7 100644 --- a/benchmarks/benchmarks/bench_function_base.py +++ b/benchmarks/benchmarks/bench_function_base.py @@ -1,11 +1,9 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np -class Histogram1D(Benchmark): +class Histogram1D(object): def setup(self): self.d = np.linspace(0, 100, 100000) @@ -19,7 +17,7 @@ def time_fine_binning(self): np.histogram(self.d, 10000, (0, 100)) -class Histogram2D(Benchmark): +class Histogram2D(object): def setup(self): self.d = np.linspace(0, 100, 200000).reshape((-1,2)) @@ -33,7 +31,7 @@ def time_fine_binning(self): np.histogramdd(self.d, (10000, 10000), ((0, 100), (0, 100))) -class Bincount(Benchmark): +class Bincount(object): def setup(self): self.d = np.arange(80000, dtype=np.intp) self.e = self.d.astype(np.float64) @@ -45,7 +43,7 @@ def time_weights(self): np.bincount(self.d, weights=self.e) -class Median(Benchmark): +class Median(object): def setup(self): self.e = np.arange(10000, dtype=np.float32) self.o = np.arange(10001, dtype=np.float32) @@ -69,7 +67,7 @@ def time_odd_small(self): np.median(self.o[:500], overwrite_input=True) -class Percentile(Benchmark): +class Percentile(object): def setup(self): self.e = np.arange(10000, dtype=np.float32) self.o = np.arange(10001, dtype=np.float32) @@ -81,7 +79,7 @@ def time_percentile(self): np.percentile(self.e, [25, 35, 55, 65, 75]) -class Select(Benchmark): +class Select(object): def setup(self): self.d = np.arange(20000) self.e = self.d.copy() @@ -95,7 +93,7 @@ def time_select_larger(self): np.select(self.cond_large, ([self.d, self.e] * 10)) -class Sort(Benchmark): +class Sort(object): def setup(self): self.e = np.arange(10000, dtype=np.float32) self.o = np.arange(10001, dtype=np.float32) @@ -138,7 +136,7 @@ def time_argsort_random(self): self.o.argsort() -class Where(Benchmark): +class Where(object): def setup(self): self.d = np.arange(20000) self.e = self.d.copy() diff --git a/benchmarks/benchmarks/bench_indexing.py b/benchmarks/benchmarks/bench_indexing.py index a62a2050e283..b058ae597432 100644 --- a/benchmarks/benchmarks/bench_indexing.py +++ b/benchmarks/benchmarks/bench_indexing.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark, get_squares_, get_indexes_, get_indexes_rand_ +from .common import get_squares_, get_indexes_, get_indexes_rand_ from os.path import join as pjoin import shutil @@ -11,7 +11,7 @@ from tempfile import mkdtemp -class Indexing(Benchmark): +class Indexing(object): params = [["indexes_", "indexes_rand_"], ['I', ':,I', 'np.ix_(I, I)'], ['', '=1']] @@ -38,7 +38,7 @@ def time_op(self, indexes, sel, op): self.func() -class IndexingSeparate(Benchmark): +class IndexingSeparate(object): def setup(self): self.tmp_dir = mkdtemp() self.fp = memmap(pjoin(self.tmp_dir, 'tmp.dat'), @@ -58,7 +58,7 @@ def time_mmap_fancy_indexing(self): self.fp[self.indexes] -class IndexingStructured0D(Benchmark): +class IndexingStructured0D(object): def setup(self): self.dt = np.dtype([('a', 'f4', 256)]) diff --git a/benchmarks/benchmarks/bench_io.py b/benchmarks/benchmarks/bench_io.py index ce30c43450cb..1fddfbc8cf6f 100644 --- a/benchmarks/benchmarks/bench_io.py +++ b/benchmarks/benchmarks/bench_io.py @@ -1,12 +1,12 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark, get_squares +from .common import get_squares import numpy as np from io import StringIO -class Copy(Benchmark): +class Copy(object): params = ["int8", "int16", "float32", "float64", "complex64", "complex128"] param_names = ['type'] @@ -31,7 +31,7 @@ def time_strided_assign(self, typename): self.dflat[::2] = 2 -class CopyTo(Benchmark): +class CopyTo(object): def setup(self): self.d = np.ones(50000) self.e = self.d.copy() @@ -57,14 +57,14 @@ def time_copyto_8_dense(self): np.copyto(self.d, self.e, where=self.im8) -class Savez(Benchmark): +class Savez(object): def setup(self): self.squares = get_squares() def time_vb_savez_squares(self): np.savez('tmp.npz', self.squares) -class LoadtxtCSVComments(Benchmark): +class LoadtxtCSVComments(object): # benchmarks for np.loadtxt comment handling # when reading in CSV files @@ -93,7 +93,7 @@ def time_comment_loadtxt_csv(self, num_lines): delimiter=u',') self.data_comments.seek(0) -class LoadtxtCSVdtypes(Benchmark): +class LoadtxtCSVdtypes(object): # benchmarks for np.loadtxt operating with # different dtypes parsed / cast from CSV files @@ -118,7 +118,7 @@ def time_loadtxt_dtypes_csv(self, dtype, num_lines): dtype=dtype) self.csv_data.seek(0) -class LoadtxtCSVStructured(Benchmark): +class LoadtxtCSVStructured(object): # benchmarks for np.loadtxt operating with # a structured data type & CSV file @@ -141,7 +141,7 @@ def time_loadtxt_csv_struct_dtype(self): self.csv_data.seek(0) -class LoadtxtCSVSkipRows(Benchmark): +class LoadtxtCSVSkipRows(object): # benchmarks for loadtxt row skipping when # reading in csv file data; a similar benchmark # is present in the pandas asv suite @@ -162,7 +162,7 @@ def time_skiprows_csv(self, skiprows): delimiter=',', skiprows=skiprows) -class LoadtxtReadUint64Integers(Benchmark): +class LoadtxtReadUint64Integers(object): # pandas has a similar CSV reading benchmark # modified to suit np.loadtxt @@ -188,7 +188,7 @@ def time_read_uint64_neg_values(self, size): np.loadtxt(self.data2) self.data2.seek(0) -class LoadtxtUseColsCSV(Benchmark): +class LoadtxtUseColsCSV(object): # benchmark selective column reading from CSV files # using np.loadtxt @@ -208,7 +208,7 @@ def time_loadtxt_usecols_csv(self, usecols): usecols=usecols) self.csv_data.seek(0) -class LoadtxtCSVDateTime(Benchmark): +class LoadtxtCSVDateTime(object): # benchmarks for np.loadtxt operating with # datetime data in a CSV file diff --git a/benchmarks/benchmarks/bench_lib.py b/benchmarks/benchmarks/bench_lib.py index 83f26c9d1170..3a79292da751 100644 --- a/benchmarks/benchmarks/bench_lib.py +++ b/benchmarks/benchmarks/bench_lib.py @@ -1,15 +1,13 @@ -"""Benchmarks for `numpy.lib`.""" +"""objects for `numpy.lib`.""" from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np -class Pad(Benchmark): - """Benchmarks for `numpy.pad`.""" +class Pad(object): + """objects for `numpy.pad`.""" param_names = ["shape", "pad_width", "mode"] params = [ diff --git a/benchmarks/benchmarks/bench_linalg.py b/benchmarks/benchmarks/bench_linalg.py index a65d510be276..67d4ce851d38 100644 --- a/benchmarks/benchmarks/bench_linalg.py +++ b/benchmarks/benchmarks/bench_linalg.py @@ -1,11 +1,11 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark, get_squares_, get_indexes_rand, TYPES1 +from .common import get_squares_, get_indexes_rand, TYPES1 import numpy as np -class Eindot(Benchmark): +class Eindot(object): def setup(self): self.a = np.arange(60000.0).reshape(150, 400) self.ac = self.a.copy() @@ -73,7 +73,7 @@ def time_tensordot_a_b_axes_1_0_0_1(self): np.tensordot(self.a3, self.b3, axes=([1, 0], [0, 1])) -class Linalg(Benchmark): +class Linalg(object): params = [['svd', 'pinv', 'det', 'norm'], TYPES1] param_names = ['op', 'type'] @@ -100,7 +100,7 @@ def time_op(self, op, typename): self.func(self.a) -class Lstsq(Benchmark): +class Lstsq(object): def setup(self): self.a = get_squares_()['float64'] self.b = get_indexes_rand()[:100].astype(np.float64) diff --git a/benchmarks/benchmarks/bench_ma.py b/benchmarks/benchmarks/bench_ma.py index d313f01dc427..848a0d419bd3 100644 --- a/benchmarks/benchmarks/bench_ma.py +++ b/benchmarks/benchmarks/bench_ma.py @@ -1,11 +1,9 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np -class MA(Benchmark): +class MA(object): def setup(self): self.l100 = range(100) self.t100 = ([True] * 100) @@ -20,7 +18,7 @@ def time_masked_array_l100_t100(self): np.ma.masked_array(self.l100, self.t100) -class Indexing(Benchmark): +class Indexing(object): param_names = ['masked', 'ndim', 'size'] params = [[True, False], [1, 2], @@ -47,7 +45,7 @@ def time_1d(self, masked, ndim, size): self.m[self.idx_1d] -class UFunc(Benchmark): +class UFunc(object): param_names = ['a_masked', 'b_masked', 'size'] params = [[True, False], [True, False], @@ -79,7 +77,7 @@ def time_2d(self, a_masked, b_masked, size): np.ma.add(self.a_2d, self.b_2d) -class Concatenate(Benchmark): +class Concatenate(object): param_names = ['mode', 'n'] params = [ ['ndarray', 'unmasked', diff --git a/benchmarks/benchmarks/bench_overrides.py b/benchmarks/benchmarks/bench_overrides.py index 2cb94c95ce60..58ba9be0458a 100644 --- a/benchmarks/benchmarks/bench_overrides.py +++ b/benchmarks/benchmarks/bench_overrides.py @@ -1,7 +1,5 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - from numpy.core.overrides import array_function_dispatch import numpy as np @@ -32,7 +30,7 @@ def __array_function__(self, func, types, args, kwargs): pass -class ArrayFunction(Benchmark): +class ArrayFunction(object): def setup(self): self.numpy_array = np.array(1) diff --git a/benchmarks/benchmarks/bench_random.py b/benchmarks/benchmarks/bench_random.py index 9d84d83d310a..240a3cd01031 100644 --- a/benchmarks/benchmarks/bench_random.py +++ b/benchmarks/benchmarks/bench_random.py @@ -1,11 +1,9 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np -class Random(Benchmark): +class Random(object): params = ['normal', 'uniform', 'weibull 1', 'binomial 10 0.5', 'poisson 10'] @@ -21,7 +19,7 @@ def time_rng(self, name): self.func(*self.params) -class Shuffle(Benchmark): +class Shuffle(object): def setup(self): self.a = np.arange(100000) @@ -29,7 +27,7 @@ def time_100000(self): np.random.shuffle(self.a) -class Randint(Benchmark): +class Randint(object): def time_randint_fast(self): """Compare to uint32 below""" @@ -40,7 +38,7 @@ def time_randint_slow(self): np.random.randint(0, 2**30 + 1, size=10**5) -class Randint_dtype(Benchmark): +class Randint_dtype(object): high = { 'bool': 1, 'uint8': 2**7, @@ -66,7 +64,7 @@ def time_randint_slow(self, name): np.random.randint(0, high + 1, size=10**5, dtype=name) -class Permutation(Benchmark): +class Permutation(object): def setup(self): self.n = 10000 self.a_1d = np.random.random_sample(self.n) diff --git a/benchmarks/benchmarks/bench_reduce.py b/benchmarks/benchmarks/bench_reduce.py index 353eb980c6de..319a4b15fa5f 100644 --- a/benchmarks/benchmarks/bench_reduce.py +++ b/benchmarks/benchmarks/bench_reduce.py @@ -1,11 +1,11 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark, TYPES1, get_squares +from .common import TYPES1, get_squares import numpy as np -class AddReduce(Benchmark): +class AddReduce(object): def setup(self): self.squares = get_squares().values() @@ -16,7 +16,7 @@ def time_axis_1(self): [np.add.reduce(a, axis=1) for a in self.squares] -class AddReduceSeparate(Benchmark): +class AddReduceSeparate(object): params = [[0, 1], TYPES1] param_names = ['axis', 'type'] @@ -27,7 +27,7 @@ def time_reduce(self, axis, typename): np.add.reduce(self.a, axis=axis) -class AnyAll(Benchmark): +class AnyAll(object): def setup(self): self.zeros = np.zeros(100000, bool) self.ones = np.ones(100000, bool) @@ -45,7 +45,7 @@ def time_any_slow(self): self.zeros.any() -class MinMax(Benchmark): +class MinMax(object): params = [np.float32, np.float64, np.intp] param_names = ['dtype'] @@ -59,7 +59,7 @@ def time_max(self, dtype): np.max(self.d) -class SmallReduction(Benchmark): +class SmallReduction(object): def setup(self): self.d = np.ones(100, dtype=np.float32) diff --git a/benchmarks/benchmarks/bench_shape_base.py b/benchmarks/benchmarks/bench_shape_base.py index b05ea8263d6c..ed88aa1fdb6d 100644 --- a/benchmarks/benchmarks/bench_shape_base.py +++ b/benchmarks/benchmarks/bench_shape_base.py @@ -1,11 +1,9 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark - import numpy as np -class Block(Benchmark): +class Block(object): params = [1, 10, 100] param_names = ['size'] @@ -65,7 +63,7 @@ def time_no_lists(self, n): np.block(np.eye(3 * n)) -class Block3D(Benchmark): +class Block3D(object): params = [1, 10, 100] param_names = ['size'] diff --git a/benchmarks/benchmarks/bench_ufunc.py b/benchmarks/benchmarks/bench_ufunc.py index a7e385f703b8..cc9e6e34d1e7 100644 --- a/benchmarks/benchmarks/bench_ufunc.py +++ b/benchmarks/benchmarks/bench_ufunc.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, division, print_function -from .common import Benchmark, get_squares_ +from .common import get_squares_ import numpy as np @@ -27,7 +27,7 @@ print("Missing ufunc %r" % (name,)) -class Broadcast(Benchmark): +class Broadcast(object): def setup(self): self.d = np.ones((50000, 100), dtype=np.float64) self.e = np.ones((100,), dtype=np.float64) @@ -36,7 +36,7 @@ def time_broadcast(self): self.d - self.e -class UFunc(Benchmark): +class UFunc(object): params = [ufuncs] param_names = ['ufunc'] timeout = 10 @@ -60,7 +60,7 @@ def time_ufunc_types(self, ufuncname): [self.f(*arg) for arg in self.args] -class Custom(Benchmark): +class Custom(object): def setup(self): self.b = np.ones(20000, dtype=bool) @@ -77,7 +77,7 @@ def time_or_bool(self): (self.b | self.b) -class CustomInplace(Benchmark): +class CustomInplace(object): def setup(self): self.c = np.ones(500000, dtype=np.int8) self.i = np.ones(150000, dtype=np.int32) @@ -116,7 +116,7 @@ def time_double_add_temp(self): 1. + self.d + 1. -class CustomScalar(Benchmark): +class CustomScalar(object): params = [np.float32, np.float64] param_names = ['dtype'] @@ -136,7 +136,7 @@ def time_less_than_scalar2(self, dtype): (self.d < 1) -class Scalar(Benchmark): +class Scalar(object): def setup(self): self.x = np.asarray(1.0) self.y = np.asarray((1.0 + 1j)) @@ -164,7 +164,7 @@ def __repr__(self): )) -class ArgParsing(Benchmark): +class ArgParsing(object): # In order to benchmark the speed of argument parsing, all but the # out arguments are chosen such that they have no effect on the # calculation. In particular, subok=True and where=True are @@ -189,7 +189,7 @@ def time_add_arg_parsing(self, arg_pack): np.add(*arg_pack.args, **arg_pack.kwargs) -class ArgParsingReduce(Benchmark): +class ArgParsingReduce(object): # In order to benchmark the speed of argument parsing, all but the # out arguments are chosen such that they have minimal effect on the # calculation. diff --git a/benchmarks/benchmarks/common.py b/benchmarks/benchmarks/common.py index d720eaaa89b8..84cb30461d00 100644 --- a/benchmarks/benchmarks/common.py +++ b/benchmarks/benchmarks/common.py @@ -110,7 +110,3 @@ def get_indexes_rand_(): indexes_rand = get_indexes_rand() indexes_rand_ = indexes_rand[indexes_rand < nxs] return indexes_rand_ - - -class Benchmark(object): - sample_time = 0.25 From 692d2b4983f27fd04ea5983db1cfe731a23d1bb9 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Mon, 24 Sep 2018 09:13:55 -0700 Subject: [PATCH 10/12] CLN: optimize ndarray.__array_function__ --- numpy/core/_methods.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/numpy/core/_methods.py b/numpy/core/_methods.py index f49f64febfad..8974f0ce1a9f 100644 --- a/numpy/core/_methods.py +++ b/numpy/core/_methods.py @@ -155,13 +155,16 @@ def _ptp(a, axis=None, out=None, keepdims=False): out ) +_NDARRAY_ARRAY_FUNCTION = mu.ndarray.__array_function__ + def _array_function(self, func, types, args, kwargs): # TODO: rewrite this in C # Cannot handle items that have __array_function__ other than our own. for t in types: - if (hasattr(t, '__array_function__') and - t.__array_function__ is not mu.ndarray.__array_function__): - return NotImplemented + if t is not mu.ndarray: + method = getattr(t, '__array_function__', _NDARRAY_ARRAY_FUNCTION) + if method is not _NDARRAY_ARRAY_FUNCTION: + return NotImplemented # Arguments contain no overrides, so we can safely call the # overloaded function again. From c0cf6170dc707a742a83f7807b35fa25132480cf Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Mon, 24 Sep 2018 11:43:10 -0700 Subject: [PATCH 11/12] Revert removal of Benchmark class --- benchmarks/benchmarks/bench_app.py | 6 ++++-- benchmarks/benchmarks/bench_core.py | 16 +++++++------- benchmarks/benchmarks/bench_function_base.py | 20 ++++++++++-------- benchmarks/benchmarks/bench_indexing.py | 8 +++---- benchmarks/benchmarks/bench_io.py | 22 ++++++++++---------- benchmarks/benchmarks/bench_lib.py | 8 ++++--- benchmarks/benchmarks/bench_linalg.py | 8 +++---- benchmarks/benchmarks/bench_ma.py | 10 +++++---- benchmarks/benchmarks/bench_overrides.py | 4 +++- benchmarks/benchmarks/bench_random.py | 12 ++++++----- benchmarks/benchmarks/bench_reduce.py | 12 +++++------ benchmarks/benchmarks/bench_shape_base.py | 6 ++++-- benchmarks/benchmarks/bench_ufunc.py | 18 ++++++++-------- benchmarks/benchmarks/common.py | 4 ++++ 14 files changed, 87 insertions(+), 67 deletions(-) diff --git a/benchmarks/benchmarks/bench_app.py b/benchmarks/benchmarks/bench_app.py index bc217c3ec03d..ccf6e4c4af85 100644 --- a/benchmarks/benchmarks/bench_app.py +++ b/benchmarks/benchmarks/bench_app.py @@ -1,11 +1,13 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np from six.moves import xrange -class LaplaceInplace(object): +class LaplaceInplace(Benchmark): params = ['inplace', 'normal'] param_names = ['update'] @@ -51,7 +53,7 @@ def time_it(self, update): self.run() -class MaxesOfDots(object): +class MaxesOfDots(Benchmark): def setup(self): np.random.seed(1) nsubj = 5 diff --git a/benchmarks/benchmarks/bench_core.py b/benchmarks/benchmarks/bench_core.py index b6cbd93508fa..26cffcab1192 100644 --- a/benchmarks/benchmarks/bench_core.py +++ b/benchmarks/benchmarks/bench_core.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np -class Core(object): +class Core(Benchmark): def setup(self): self.l100 = range(100) self.l50 = range(50) @@ -74,7 +76,7 @@ def time_tril_l10x10(self): np.tril(self.l10x10) -class Temporaries(object): +class Temporaries(Benchmark): def setup(self): self.amid = np.ones(50000) self.bmid = np.ones(50000) @@ -94,7 +96,7 @@ def time_large2(self): (self.alarge + self.blarge) - 2 -class CorrConv(object): +class CorrConv(Benchmark): params = [[50, 1000, 1e5], [10, 100, 1000, 1e4], ['valid', 'same', 'full']] @@ -111,7 +113,7 @@ def time_convolve(self, size1, size2, mode): np.convolve(self.x1, self.x2, mode=mode) -class CountNonzero(object): +class CountNonzero(Benchmark): param_names = ['numaxes', 'size', 'dtype'] params = [ [1, 2, 3], @@ -135,7 +137,7 @@ def time_count_nonzero_multi_axis(self, numaxes, size, dtype): self.x.ndim - 1, self.x.ndim - 2)) -class PackBits(object): +class PackBits(Benchmark): param_names = ['dtype'] params = [[bool, np.uintp]] def setup(self, dtype): @@ -152,7 +154,7 @@ def time_packbits_axis1(self, dtype): np.packbits(self.d2, axis=1) -class UnpackBits(object): +class UnpackBits(Benchmark): def setup(self): self.d = np.ones(10000, dtype=np.uint8) self.d2 = np.ones((200, 1000), dtype=np.uint8) @@ -167,6 +169,6 @@ def time_unpackbits_axis1(self): np.unpackbits(self.d2, axis=1) -class Indices(object): +class Indices(Benchmark): def time_indices(self): np.indices((1000, 500)) diff --git a/benchmarks/benchmarks/bench_function_base.py b/benchmarks/benchmarks/bench_function_base.py index 7f217fc905e5..9ef03262bb16 100644 --- a/benchmarks/benchmarks/bench_function_base.py +++ b/benchmarks/benchmarks/bench_function_base.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np -class Histogram1D(object): +class Histogram1D(Benchmark): def setup(self): self.d = np.linspace(0, 100, 100000) @@ -17,7 +19,7 @@ def time_fine_binning(self): np.histogram(self.d, 10000, (0, 100)) -class Histogram2D(object): +class Histogram2D(Benchmark): def setup(self): self.d = np.linspace(0, 100, 200000).reshape((-1,2)) @@ -31,7 +33,7 @@ def time_fine_binning(self): np.histogramdd(self.d, (10000, 10000), ((0, 100), (0, 100))) -class Bincount(object): +class Bincount(Benchmark): def setup(self): self.d = np.arange(80000, dtype=np.intp) self.e = self.d.astype(np.float64) @@ -43,7 +45,7 @@ def time_weights(self): np.bincount(self.d, weights=self.e) -class Median(object): +class Median(Benchmark): def setup(self): self.e = np.arange(10000, dtype=np.float32) self.o = np.arange(10001, dtype=np.float32) @@ -67,7 +69,7 @@ def time_odd_small(self): np.median(self.o[:500], overwrite_input=True) -class Percentile(object): +class Percentile(Benchmark): def setup(self): self.e = np.arange(10000, dtype=np.float32) self.o = np.arange(10001, dtype=np.float32) @@ -79,7 +81,7 @@ def time_percentile(self): np.percentile(self.e, [25, 35, 55, 65, 75]) -class Select(object): +class Select(Benchmark): def setup(self): self.d = np.arange(20000) self.e = self.d.copy() @@ -93,7 +95,7 @@ def time_select_larger(self): np.select(self.cond_large, ([self.d, self.e] * 10)) -class Sort(object): +class Sort(Benchmark): def setup(self): self.e = np.arange(10000, dtype=np.float32) self.o = np.arange(10001, dtype=np.float32) @@ -125,7 +127,7 @@ def time_argsort_random(self): self.o.argsort() -class SortWorst(object): +class SortWorst(Benchmark): def setup(self): # quicksort median of 3 worst case self.worst = np.arange(1000000) @@ -142,7 +144,7 @@ def time_sort_worst(self): time_sort_worst.benchmark_name = "bench_function_base.Sort.time_sort_worst" -class Where(object): +class Where(Benchmark): def setup(self): self.d = np.arange(20000) self.e = self.d.copy() diff --git a/benchmarks/benchmarks/bench_indexing.py b/benchmarks/benchmarks/bench_indexing.py index b058ae597432..a62a2050e283 100644 --- a/benchmarks/benchmarks/bench_indexing.py +++ b/benchmarks/benchmarks/bench_indexing.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, division, print_function -from .common import get_squares_, get_indexes_, get_indexes_rand_ +from .common import Benchmark, get_squares_, get_indexes_, get_indexes_rand_ from os.path import join as pjoin import shutil @@ -11,7 +11,7 @@ from tempfile import mkdtemp -class Indexing(object): +class Indexing(Benchmark): params = [["indexes_", "indexes_rand_"], ['I', ':,I', 'np.ix_(I, I)'], ['', '=1']] @@ -38,7 +38,7 @@ def time_op(self, indexes, sel, op): self.func() -class IndexingSeparate(object): +class IndexingSeparate(Benchmark): def setup(self): self.tmp_dir = mkdtemp() self.fp = memmap(pjoin(self.tmp_dir, 'tmp.dat'), @@ -58,7 +58,7 @@ def time_mmap_fancy_indexing(self): self.fp[self.indexes] -class IndexingStructured0D(object): +class IndexingStructured0D(Benchmark): def setup(self): self.dt = np.dtype([('a', 'f4', 256)]) diff --git a/benchmarks/benchmarks/bench_io.py b/benchmarks/benchmarks/bench_io.py index da29cfe73e25..879f9b69ebc8 100644 --- a/benchmarks/benchmarks/bench_io.py +++ b/benchmarks/benchmarks/bench_io.py @@ -1,12 +1,12 @@ from __future__ import absolute_import, division, print_function -from .common import get_squares +from .common import Benchmark, get_squares import numpy as np from io import StringIO -class Copy(object): +class Copy(Benchmark): params = ["int8", "int16", "float32", "float64", "complex64", "complex128"] param_names = ['type'] @@ -35,7 +35,7 @@ def time_strided_assign(self, typename): self.dflat[::2] = 2 -class CopyTo(object): +class CopyTo(Benchmark): def setup(self): self.d = np.ones(50000) self.e = self.d.copy() @@ -61,14 +61,14 @@ def time_copyto_8_dense(self): np.copyto(self.d, self.e, where=self.im8) -class Savez(object): +class Savez(Benchmark): def setup(self): self.squares = get_squares() def time_vb_savez_squares(self): np.savez('tmp.npz', self.squares) -class LoadtxtCSVComments(object): +class LoadtxtCSVComments(Benchmark): # benchmarks for np.loadtxt comment handling # when reading in CSV files @@ -97,7 +97,7 @@ def time_comment_loadtxt_csv(self, num_lines): delimiter=u',') self.data_comments.seek(0) -class LoadtxtCSVdtypes(object): +class LoadtxtCSVdtypes(Benchmark): # benchmarks for np.loadtxt operating with # different dtypes parsed / cast from CSV files @@ -122,7 +122,7 @@ def time_loadtxt_dtypes_csv(self, dtype, num_lines): dtype=dtype) self.csv_data.seek(0) -class LoadtxtCSVStructured(object): +class LoadtxtCSVStructured(Benchmark): # benchmarks for np.loadtxt operating with # a structured data type & CSV file @@ -145,7 +145,7 @@ def time_loadtxt_csv_struct_dtype(self): self.csv_data.seek(0) -class LoadtxtCSVSkipRows(object): +class LoadtxtCSVSkipRows(Benchmark): # benchmarks for loadtxt row skipping when # reading in csv file data; a similar benchmark # is present in the pandas asv suite @@ -166,7 +166,7 @@ def time_skiprows_csv(self, skiprows): delimiter=',', skiprows=skiprows) -class LoadtxtReadUint64Integers(object): +class LoadtxtReadUint64Integers(Benchmark): # pandas has a similar CSV reading benchmark # modified to suit np.loadtxt @@ -192,7 +192,7 @@ def time_read_uint64_neg_values(self, size): np.loadtxt(self.data2) self.data2.seek(0) -class LoadtxtUseColsCSV(object): +class LoadtxtUseColsCSV(Benchmark): # benchmark selective column reading from CSV files # using np.loadtxt @@ -212,7 +212,7 @@ def time_loadtxt_usecols_csv(self, usecols): usecols=usecols) self.csv_data.seek(0) -class LoadtxtCSVDateTime(object): +class LoadtxtCSVDateTime(Benchmark): # benchmarks for np.loadtxt operating with # datetime data in a CSV file diff --git a/benchmarks/benchmarks/bench_lib.py b/benchmarks/benchmarks/bench_lib.py index fa6c56708240..e6c91a27c1a9 100644 --- a/benchmarks/benchmarks/bench_lib.py +++ b/benchmarks/benchmarks/bench_lib.py @@ -1,13 +1,15 @@ -"""objects for `numpy.lib`.""" +"""Benchmarks for `numpy.lib`.""" from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np -class Pad(object): - """objects for `numpy.pad`.""" +class Pad(Benchmark): + """Benchmarks for `numpy.pad`.""" param_names = ["shape", "pad_width", "mode"] params = [ diff --git a/benchmarks/benchmarks/bench_linalg.py b/benchmarks/benchmarks/bench_linalg.py index 67d4ce851d38..a65d510be276 100644 --- a/benchmarks/benchmarks/bench_linalg.py +++ b/benchmarks/benchmarks/bench_linalg.py @@ -1,11 +1,11 @@ from __future__ import absolute_import, division, print_function -from .common import get_squares_, get_indexes_rand, TYPES1 +from .common import Benchmark, get_squares_, get_indexes_rand, TYPES1 import numpy as np -class Eindot(object): +class Eindot(Benchmark): def setup(self): self.a = np.arange(60000.0).reshape(150, 400) self.ac = self.a.copy() @@ -73,7 +73,7 @@ def time_tensordot_a_b_axes_1_0_0_1(self): np.tensordot(self.a3, self.b3, axes=([1, 0], [0, 1])) -class Linalg(object): +class Linalg(Benchmark): params = [['svd', 'pinv', 'det', 'norm'], TYPES1] param_names = ['op', 'type'] @@ -100,7 +100,7 @@ def time_op(self, op, typename): self.func(self.a) -class Lstsq(object): +class Lstsq(Benchmark): def setup(self): self.a = get_squares_()['float64'] self.b = get_indexes_rand()[:100].astype(np.float64) diff --git a/benchmarks/benchmarks/bench_ma.py b/benchmarks/benchmarks/bench_ma.py index 631b793b1917..aff78df0a5aa 100644 --- a/benchmarks/benchmarks/bench_ma.py +++ b/benchmarks/benchmarks/bench_ma.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np -class MA(object): +class MA(Benchmark): def setup(self): self.l100 = range(100) self.t100 = ([True] * 100) @@ -18,7 +20,7 @@ def time_masked_array_l100_t100(self): np.ma.masked_array(self.l100, self.t100) -class Indexing(object): +class Indexing(Benchmark): param_names = ['masked', 'ndim', 'size'] params = [[True, False], [1, 2], @@ -45,7 +47,7 @@ def time_1d(self, masked, ndim, size): self.m[self.idx_1d] -class UFunc(object): +class UFunc(Benchmark): param_names = ['a_masked', 'b_masked', 'size'] params = [[True, False], [True, False], @@ -77,7 +79,7 @@ def time_2d(self, a_masked, b_masked, size): np.ma.add(self.a_2d, self.b_2d) -class Concatenate(object): +class Concatenate(Benchmark): param_names = ['mode', 'n'] params = [ ['ndarray', 'unmasked', diff --git a/benchmarks/benchmarks/bench_overrides.py b/benchmarks/benchmarks/bench_overrides.py index 58ba9be0458a..2cb94c95ce60 100644 --- a/benchmarks/benchmarks/bench_overrides.py +++ b/benchmarks/benchmarks/bench_overrides.py @@ -1,5 +1,7 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + from numpy.core.overrides import array_function_dispatch import numpy as np @@ -30,7 +32,7 @@ def __array_function__(self, func, types, args, kwargs): pass -class ArrayFunction(object): +class ArrayFunction(Benchmark): def setup(self): self.numpy_array = np.array(1) diff --git a/benchmarks/benchmarks/bench_random.py b/benchmarks/benchmarks/bench_random.py index 240a3cd01031..9d84d83d310a 100644 --- a/benchmarks/benchmarks/bench_random.py +++ b/benchmarks/benchmarks/bench_random.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np -class Random(object): +class Random(Benchmark): params = ['normal', 'uniform', 'weibull 1', 'binomial 10 0.5', 'poisson 10'] @@ -19,7 +21,7 @@ def time_rng(self, name): self.func(*self.params) -class Shuffle(object): +class Shuffle(Benchmark): def setup(self): self.a = np.arange(100000) @@ -27,7 +29,7 @@ def time_100000(self): np.random.shuffle(self.a) -class Randint(object): +class Randint(Benchmark): def time_randint_fast(self): """Compare to uint32 below""" @@ -38,7 +40,7 @@ def time_randint_slow(self): np.random.randint(0, 2**30 + 1, size=10**5) -class Randint_dtype(object): +class Randint_dtype(Benchmark): high = { 'bool': 1, 'uint8': 2**7, @@ -64,7 +66,7 @@ def time_randint_slow(self, name): np.random.randint(0, high + 1, size=10**5, dtype=name) -class Permutation(object): +class Permutation(Benchmark): def setup(self): self.n = 10000 self.a_1d = np.random.random_sample(self.n) diff --git a/benchmarks/benchmarks/bench_reduce.py b/benchmarks/benchmarks/bench_reduce.py index 95804666eca4..ffc148cd27a7 100644 --- a/benchmarks/benchmarks/bench_reduce.py +++ b/benchmarks/benchmarks/bench_reduce.py @@ -1,11 +1,11 @@ from __future__ import absolute_import, division, print_function -from .common import TYPES1, get_squares +from .common import Benchmark, TYPES1, get_squares import numpy as np -class AddReduce(object): +class AddReduce(Benchmark): def setup(self): self.squares = get_squares().values() @@ -16,7 +16,7 @@ def time_axis_1(self): [np.add.reduce(a, axis=1) for a in self.squares] -class AddReduceSeparate(object): +class AddReduceSeparate(Benchmark): params = [[0, 1], TYPES1] param_names = ['axis', 'type'] @@ -27,7 +27,7 @@ def time_reduce(self, axis, typename): np.add.reduce(self.a, axis=axis) -class AnyAll(object): +class AnyAll(Benchmark): def setup(self): # avoid np.zeros's lazy allocation that would # cause page faults during benchmark @@ -47,7 +47,7 @@ def time_any_slow(self): self.zeros.any() -class MinMax(object): +class MinMax(Benchmark): params = [np.float32, np.float64, np.intp] param_names = ['dtype'] @@ -61,7 +61,7 @@ def time_max(self, dtype): np.max(self.d) -class SmallReduction(object): +class SmallReduction(Benchmark): def setup(self): self.d = np.ones(100, dtype=np.float32) diff --git a/benchmarks/benchmarks/bench_shape_base.py b/benchmarks/benchmarks/bench_shape_base.py index cc6dae987a9b..6edad2ea315e 100644 --- a/benchmarks/benchmarks/bench_shape_base.py +++ b/benchmarks/benchmarks/bench_shape_base.py @@ -1,9 +1,11 @@ from __future__ import absolute_import, division, print_function +from .common import Benchmark + import numpy as np -class Block(object): +class Block(Benchmark): params = [1, 10, 100] param_names = ['size'] @@ -67,7 +69,7 @@ def time_no_lists(self, n): np.block(np.eye(3 * n)) -class Block3D(object): +class Block3D(Benchmark): params = [1, 10, 100] param_names = ['size'] diff --git a/benchmarks/benchmarks/bench_ufunc.py b/benchmarks/benchmarks/bench_ufunc.py index cc9e6e34d1e7..a7e385f703b8 100644 --- a/benchmarks/benchmarks/bench_ufunc.py +++ b/benchmarks/benchmarks/bench_ufunc.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, division, print_function -from .common import get_squares_ +from .common import Benchmark, get_squares_ import numpy as np @@ -27,7 +27,7 @@ print("Missing ufunc %r" % (name,)) -class Broadcast(object): +class Broadcast(Benchmark): def setup(self): self.d = np.ones((50000, 100), dtype=np.float64) self.e = np.ones((100,), dtype=np.float64) @@ -36,7 +36,7 @@ def time_broadcast(self): self.d - self.e -class UFunc(object): +class UFunc(Benchmark): params = [ufuncs] param_names = ['ufunc'] timeout = 10 @@ -60,7 +60,7 @@ def time_ufunc_types(self, ufuncname): [self.f(*arg) for arg in self.args] -class Custom(object): +class Custom(Benchmark): def setup(self): self.b = np.ones(20000, dtype=bool) @@ -77,7 +77,7 @@ def time_or_bool(self): (self.b | self.b) -class CustomInplace(object): +class CustomInplace(Benchmark): def setup(self): self.c = np.ones(500000, dtype=np.int8) self.i = np.ones(150000, dtype=np.int32) @@ -116,7 +116,7 @@ def time_double_add_temp(self): 1. + self.d + 1. -class CustomScalar(object): +class CustomScalar(Benchmark): params = [np.float32, np.float64] param_names = ['dtype'] @@ -136,7 +136,7 @@ def time_less_than_scalar2(self, dtype): (self.d < 1) -class Scalar(object): +class Scalar(Benchmark): def setup(self): self.x = np.asarray(1.0) self.y = np.asarray((1.0 + 1j)) @@ -164,7 +164,7 @@ def __repr__(self): )) -class ArgParsing(object): +class ArgParsing(Benchmark): # In order to benchmark the speed of argument parsing, all but the # out arguments are chosen such that they have no effect on the # calculation. In particular, subok=True and where=True are @@ -189,7 +189,7 @@ def time_add_arg_parsing(self, arg_pack): np.add(*arg_pack.args, **arg_pack.kwargs) -class ArgParsingReduce(object): +class ArgParsingReduce(Benchmark): # In order to benchmark the speed of argument parsing, all but the # out arguments are chosen such that they have minimal effect on the # calculation. diff --git a/benchmarks/benchmarks/common.py b/benchmarks/benchmarks/common.py index 84cb30461d00..d720eaaa89b8 100644 --- a/benchmarks/benchmarks/common.py +++ b/benchmarks/benchmarks/common.py @@ -110,3 +110,7 @@ def get_indexes_rand_(): indexes_rand = get_indexes_rand() indexes_rand_ = indexes_rand[indexes_rand < nxs] return indexes_rand_ + + +class Benchmark(object): + sample_time = 0.25 From fbc6ad4007df5029bd079a99eaa123ed843a3ebc Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Mon, 24 Sep 2018 11:45:21 -0700 Subject: [PATCH 12/12] Revert goal_time -> sample_time --- benchmarks/benchmarks/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/benchmarks/common.py b/benchmarks/benchmarks/common.py index d720eaaa89b8..18a09fd40551 100644 --- a/benchmarks/benchmarks/common.py +++ b/benchmarks/benchmarks/common.py @@ -113,4 +113,4 @@ def get_indexes_rand_(): class Benchmark(object): - sample_time = 0.25 + goal_time = 0.25