summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc1
-rw-r--r--doc/neps/nep-template.rst29
-rw-r--r--doc/source/reference/arrays.classes.rst126
-rw-r--r--doc/source/user/basics.dispatch.rst8
-rw-r--r--doc/source/user/basics.rst1
-rw-r--r--numpy/core/_add_newdocs.py26
-rw-r--r--numpy/core/function_base.py40
-rw-r--r--numpy/doc/broadcasting.py4
-rw-r--r--numpy/doc/dispatch.py271
-rw-r--r--numpy/fft/pocketfft.py42
-rw-r--r--numpy/polynomial/polyutils.py1
-rw-r--r--numpy/polynomial/tests/test_classes.py13
-rw-r--r--numpy/polynomial/tests/test_polynomial.py4
-rwxr-xr-xtools/test-installed-numpy.py66
-rwxr-xr-xtools/travis-test.sh4
-rw-r--r--tox.ini6
16 files changed, 539 insertions, 103 deletions
diff --git a/.coveragerc b/.coveragerc
index 1f61c25a4..9048b9cc4 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,3 +1,4 @@
[run]
branch = True
include = */numpy/*
+disable_warnings = include-ignored
diff --git a/doc/neps/nep-template.rst b/doc/neps/nep-template.rst
index e869ebae3..2b49ec709 100644
--- a/doc/neps/nep-template.rst
+++ b/doc/neps/nep-template.rst
@@ -8,19 +8,36 @@ NEP Template and Instructions
:Created: <date created on, in yyyy-mm-dd format>
:Resolution: <url> (required for Accepted | Rejected | Withdrawn)
+
Abstract
--------
The abstract should be a short description of what the NEP will achieve.
+Motivation and Scope
+--------------------
+
+This section describes the need for the proposed change. It should describe
+the existing problem, who it affects, what it is trying to solve, and why.
+This section should explicitly address the scope of and key requirements for
+the proposed change.
+
+
Detailed description
--------------------
-This section describes the need for the NEP. It should describe the existing
-problem that it is trying to solve and why this NEP makes the situation better.
-It should include examples of how the new functionality would be used and
-perhaps some use cases.
+This section should provide a detailed description of the proposed change.
+It should include examples of how the new functionality would be used,
+intended use-cases and pseudo-code illustrating its use.
+
+
+Related Work
+------------
+
+This section should list relevant and/or similar technologies, possibly in other
+libraries. It does not need to be comprehensive, just list the major examples of
+prior and relevant art.
Implementation
@@ -28,8 +45,8 @@ Implementation
This section lists the major steps required to implement the NEP. Where
possible, it should be noted where one step is dependent on another, and which
-steps may be optionally omitted. Where it makes sense, each step should
-include a link related pull requests as the implementation progresses.
+steps may be optionally omitted. Where it makes sense, each step should
+include a link to related pull requests as the implementation progresses.
Any pull requests or development branches containing work on this NEP should
be linked to from here. (A NEP does not need to be implemented in a single
diff --git a/doc/source/reference/arrays.classes.rst b/doc/source/reference/arrays.classes.rst
index 3b13530c7..a91215476 100644
--- a/doc/source/reference/arrays.classes.rst
+++ b/doc/source/reference/arrays.classes.rst
@@ -6,8 +6,15 @@ Standard array subclasses
.. currentmodule:: numpy
-The :class:`ndarray` in NumPy is a "new-style" Python
-built-in-type. Therefore, it can be inherited from (in Python or in C)
+.. note::
+
+ Subclassing a ``numpy.ndarray`` is possible but if your goal is to create
+ an array with *modified* behavior, as do dask arrays for distributed
+ computation and cupy arrays for GPU-based computation, subclassing is
+ discouraged. Instead, using numpy's
+ :ref:`dispatch mechanism <basics.dispatch>` is recommended.
+
+The :class:`ndarray` can be inherited from (in Python or in C)
if desired. Therefore, it can form a foundation for many useful
classes. Often whether to sub-class the array object or to simply use
the core array component as an internal part of a new class is a
@@ -147,6 +154,121 @@ NumPy provides several hooks that classes can customize:
:func:`__array_prepare__`, :data:`__array_priority__` mechanism
described below for ufuncs (which may eventually be deprecated).
+.. py:method:: class.__array_function__(func, types, args, kwargs)
+
+ .. versionadded:: 1.16
+
+ .. note::
+
+ - In NumPy 1.17, the protocol is enabled by default, but can be disabled
+ with ``NUMPY_EXPERIMENTAL_ARRAY_FUNCTION=0``.
+ - In NumPy 1.16, you need to set the environment variable
+ ``NUMPY_EXPERIMENTAL_ARRAY_FUNCTION=1`` before importing NumPy to use
+ NumPy function overrides.
+ - Eventually, expect to ``__array_function__`` to always be enabled.
+
+ - ``func`` is an arbitrary callable exposed by NumPy's public API,
+ which was called in the form ``func(*args, **kwargs)``.
+ - ``types`` is a `collection <collections.abc.Collection>`_
+ of unique argument types from the original NumPy function call that
+ implement ``__array_function__``.
+ - The tuple ``args`` and dict ``kwargs`` are directly passed on from the
+ original call.
+
+ As a convenience for ``__array_function__`` implementors, ``types``
+ provides all argument types with an ``'__array_function__'`` attribute.
+ This allows implementors to quickly identify cases where they should defer
+ to ``__array_function__`` implementations on other arguments.
+ Implementations should not rely on the iteration order of ``types``.
+
+ Most implementations of ``__array_function__`` will start with two
+ checks:
+
+ 1. Is the given function something that we know how to overload?
+ 2. Are all arguments of a type that we know how to handle?
+
+ If these conditions hold, ``__array_function__`` should return the result
+ from calling its implementation for ``func(*args, **kwargs)``. Otherwise,
+ it should return the sentinel value ``NotImplemented``, indicating that the
+ function is not implemented by these types.
+
+ There are no general requirements on the return value from
+ ``__array_function__``, although most sensible implementations should
+ probably return array(s) with the same type as one of the function's
+ arguments.
+
+ It may also be convenient to define a custom decorators (``implements``
+ below) for registering ``__array_function__`` implementations.
+
+ .. code:: python
+
+ HANDLED_FUNCTIONS = {}
+
+ class MyArray:
+ def __array_function__(self, func, types, args, kwargs):
+ if func not in HANDLED_FUNCTIONS:
+ return NotImplemented
+ # Note: this allows subclasses that don't override
+ # __array_function__ to handle MyArray objects
+ 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__ implementation for MyArray objects."""
+ def decorator(func):
+ HANDLED_FUNCTIONS[numpy_function] = func
+ return func
+ return decorator
+
+ @implements(np.concatenate)
+ def concatenate(arrays, axis=0, out=None):
+ ... # implementation of concatenate for MyArray objects
+
+ @implements(np.broadcast_to)
+ def broadcast_to(array, shape):
+ ... # implementation of broadcast_to for MyArray objects
+
+ Note that it is not required for ``__array_function__`` implementations to
+ include *all* of the corresponding NumPy function's optional arguments
+ (e.g., ``broadcast_to`` above omits the irrelevant ``subok`` argument).
+ Optional arguments are only passed in to ``__array_function__`` if they
+ were explicitly used in the NumPy function call.
+
+ Just like the case for builtin special methods like ``__add__``, properly
+ written ``__array_function__`` methods should always return
+ ``NotImplemented`` when an unknown type is encountered. Otherwise, it will
+ be impossible to correctly override NumPy functions from another object
+ if the operation also includes one of your objects.
+
+ For the most part, the rules for dispatch with ``__array_function__``
+ match those for ``__array_ufunc__``. In particular:
+
+ - NumPy will gather implementations of ``__array_function__`` from all
+ specified inputs and call them in order: subclasses before
+ superclasses, and otherwise left to right. Note that in some edge cases
+ involving subclasses, this differs slightly from the
+ `current behavior <https://bugs.python.org/issue30140>`_ of Python.
+ - Implementations of ``__array_function__`` indicate that they can
+ handle the operation by returning any value other than
+ ``NotImplemented``.
+ - If all ``__array_function__`` methods return ``NotImplemented``,
+ NumPy will raise ``TypeError``.
+
+ If no ``__array_function__`` methods exists, NumPy will default to calling
+ its own implementation, intended for use on NumPy arrays. This case arises,
+ for example, when all array-like arguments are Python numbers or lists.
+ (NumPy arrays do have a ``__array_function__`` method, given below, but it
+ always returns ``NotImplemented`` if any argument other than a NumPy array
+ subclass implements ``__array_function__``.)
+
+ One deviation from the current behavior of ``__array_ufunc__`` is that
+ NumPy will only call ``__array_function__`` on the *first* argument of each
+ unique type. This matches Python's `rule for calling reflected methods
+ <https://docs.python.org/3/reference/datamodel.html#object.__ror__>`_, and
+ this ensures that checking overloads has acceptable performance even when
+ there are a large number of overloaded arguments.
+
.. py:method:: class.__array_finalize__(obj)
This method is called whenever the system internally allocates a
diff --git a/doc/source/user/basics.dispatch.rst b/doc/source/user/basics.dispatch.rst
new file mode 100644
index 000000000..f7b8da262
--- /dev/null
+++ b/doc/source/user/basics.dispatch.rst
@@ -0,0 +1,8 @@
+.. _basics.dispatch:
+
+*******************************
+Writing custom array containers
+*******************************
+
+.. automodule:: numpy.doc.dispatch
+
diff --git a/doc/source/user/basics.rst b/doc/source/user/basics.rst
index 7875aff6e..e0fc0ece3 100644
--- a/doc/source/user/basics.rst
+++ b/doc/source/user/basics.rst
@@ -12,4 +12,5 @@ NumPy basics
basics.broadcasting
basics.byteswapping
basics.rec
+ basics.dispatch
basics.subclassing
diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py
index bb0c77471..f041e0cd6 100644
--- a/numpy/core/_add_newdocs.py
+++ b/numpy/core/_add_newdocs.py
@@ -795,8 +795,7 @@ add_newdoc('numpy.core.multiarray', 'array',
dtype : data-type, optional
The desired data-type for the array. If not given, then the type will
be determined as the minimum type required to hold the objects in the
- sequence. This argument can only be used to 'upcast' the array. For
- downcasting, use the .astype(t) method.
+ sequence.
copy : bool, optional
If true (default), then the object is copied. Otherwise, a copy will
only be made if __array__ returns a copy, if obj is a nested sequence,
@@ -2736,6 +2735,8 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('byteswap',
Toggle between low-endian and big-endian data representation by
returning a byteswapped array, optionally swapped in-place.
+ Arrays of byte-strings are not swapped. The real and imaginary
+ parts of a complex number are swapped individually.
Parameters
----------
@@ -2758,13 +2759,24 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('byteswap',
>>> list(map(hex, A))
['0x100', '0x1', '0x3322']
- Arrays of strings are not swapped
+ Arrays of byte-strings are not swapped
- >>> A = np.array(['ceg', 'fac'])
+ >>> A = np.array([b'ceg', b'fac'])
>>> A.byteswap()
- Traceback (most recent call last):
- ...
- UnicodeDecodeError: ...
+ array([b'ceg', b'fac'], dtype='|S3')
+
+ ``A.newbyteorder().byteswap()`` produces an array with the same values
+ but different representation in memory
+
+ >>> A = np.array([1, 2, 3])
+ >>> A.view(np.uint8)
+ array([1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0], dtype=uint8)
+ >>> A.newbyteorder().byteswap(inplace=True)
+ array([1, 2, 3])
+ >>> A.view(np.uint8)
+ array([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 3], dtype=uint8)
"""))
diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py
index 0dd6ee420..d83af9911 100644
--- a/numpy/core/function_base.py
+++ b/numpy/core/function_base.py
@@ -3,6 +3,7 @@ from __future__ import division, absolute_import, print_function
import functools
import warnings
import operator
+import types
from . import numeric as _nx
from .numeric import (result_type, NaN, shares_memory, MAY_SHARE_BOUNDS,
@@ -430,15 +431,39 @@ def geomspace(start, stop, num=50, endpoint=True, dtype=None, axis=0):
return result.astype(dtype, copy=False)
-#always succeed
-def _add_docstring(obj, doc):
+def _needs_add_docstring(obj):
+ """
+ Returns true if the only way to set the docstring of `obj` from python is
+ via add_docstring.
+
+ This function errs on the side of being overly conservative.
+ """
+ Py_TPFLAGS_HEAPTYPE = 1 << 9
+
+ if isinstance(obj, (types.FunctionType, types.MethodType, property)):
+ return False
+
+ if isinstance(obj, type) and obj.__flags__ & Py_TPFLAGS_HEAPTYPE:
+ return False
+
+ return True
+
+
+def _add_docstring(obj, doc, warn_on_python):
+ if warn_on_python and not _needs_add_docstring(obj):
+ warnings.warn(
+ "add_newdoc was used on a pure-python object {}. "
+ "Prefer to attach it directly to the source."
+ .format(obj),
+ UserWarning,
+ stacklevel=3)
try:
add_docstring(obj, doc)
except Exception:
pass
-def add_newdoc(place, obj, doc):
+def add_newdoc(place, obj, doc, warn_on_python=True):
"""
Add documentation to an existing object, typically one defined in C
@@ -460,6 +485,9 @@ def add_newdoc(place, obj, doc):
If a list, then each element of the list should be a tuple of length
two - ``[(method1, docstring1), (method2, docstring2), ...]``
+ warn_on_python : bool
+ If True, the default, emit `UserWarning` if this is used to attach
+ documentation to a pure-python object.
Notes
-----
@@ -483,10 +511,10 @@ def add_newdoc(place, obj, doc):
"""
new = getattr(__import__(place, globals(), {}, [obj]), obj)
if isinstance(doc, str):
- _add_docstring(new, doc.strip())
+ _add_docstring(new, doc.strip(), warn_on_python)
elif isinstance(doc, tuple):
attr, docstring = doc
- _add_docstring(getattr(new, attr), docstring.strip())
+ _add_docstring(getattr(new, attr), docstring.strip(), warn_on_python)
elif isinstance(doc, list):
for attr, docstring in doc:
- _add_docstring(getattr(new, attr), docstring.strip())
+ _add_docstring(getattr(new, attr), docstring.strip(), warn_on_python)
diff --git a/numpy/doc/broadcasting.py b/numpy/doc/broadcasting.py
index 0bdb6ae7d..f7bd2515b 100644
--- a/numpy/doc/broadcasting.py
+++ b/numpy/doc/broadcasting.py
@@ -42,7 +42,7 @@ We can think of the scalar ``b`` being *stretched* during the arithmetic
operation into an array with the same shape as ``a``. The new elements in
``b`` are simply copies of the original scalar. The stretching analogy is
only conceptual. NumPy is smart enough to use the original scalar value
-without actually making copies, so that broadcasting operations are as
+without actually making copies so that broadcasting operations are as
memory and computationally efficient as possible.
The code in the second example is more efficient than that in the first
@@ -52,7 +52,7 @@ because broadcasting moves less memory around during the multiplication
General Broadcasting Rules
==========================
When operating on two arrays, NumPy compares their shapes element-wise.
-It starts with the trailing dimensions, and works its way forward. Two
+It starts with the trailing dimensions and works its way forward. Two
dimensions are compatible when
1) they are equal, or
diff --git a/numpy/doc/dispatch.py b/numpy/doc/dispatch.py
new file mode 100644
index 000000000..09a3e5134
--- /dev/null
+++ b/numpy/doc/dispatch.py
@@ -0,0 +1,271 @@
+""".. _dispatch_mechanism:
+
+Numpy's dispatch mechanism, introduced in numpy version v1.16 is the
+recommended approach for writing custom N-dimensional array containers that are
+compatible with the numpy API and provide custom implementations of numpy
+functionality. Applications include `dask <http://dask.pydata.org>`_ arrays, an
+N-dimensional array distributed across multiple nodes, and `cupy
+<https://docs-cupy.chainer.org/en/stable/>`_ arrays, an N-dimensional array on
+a GPU.
+
+To get a feel for writing custom array containers, we'll begin with a simple
+example that has rather narrow utility but illustrates the concepts involved.
+
+>>> import numpy as np
+>>> class DiagonalArray:
+... def __init__(self, N, value):
+... self._N = N
+... self._i = value
+... def __repr__(self):
+... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
+... def __array__(self):
+... return self._i * np.eye(self._N)
+...
+
+Our custom array can be instantiated like:
+
+>>> arr = DiagonalArray(5, 1)
+>>> arr
+DiagonalArray(N=5, value=1)
+
+We can convert to a numpy array using :func:`numpy.array` or
+:func:`numpy.asarray`, which will call its ``__array__`` method to obtain a
+standard ``numpy.ndarray``.
+
+>>> np.asarray(arr)
+array([[1., 0., 0., 0., 0.],
+ [0., 1., 0., 0., 0.],
+ [0., 0., 1., 0., 0.],
+ [0., 0., 0., 1., 0.],
+ [0., 0., 0., 0., 1.]])
+
+If we operate on ``arr`` with a numpy function, numpy will again use the
+``__array__`` interface to convert it to an array and then apply the function
+in the usual way.
+
+>>> np.multiply(arr, 2)
+array([[2., 0., 0., 0., 0.],
+ [0., 2., 0., 0., 0.],
+ [0., 0., 2., 0., 0.],
+ [0., 0., 0., 2., 0.],
+ [0., 0., 0., 0., 2.]])
+
+
+Notice that the return type is a standard ``numpy.ndarray``.
+
+>>> type(arr)
+numpy.ndarray
+
+How can we pass our custom array type through this function? Numpy allows a
+class to indicate that it would like to handle computations in a custom-defined
+way through the interaces ``__array_ufunc__`` and ``__array_function__``. Let's
+take one at a time, starting with ``_array_ufunc__``. This method covers
+:ref:`ufuncs`, a class of functions that includes, for example,
+:func:`numpy.multiply` and :func:`numpy.sin`.
+
+The ``__array_ufunc__`` receives:
+
+- ``ufunc``, a function like ``numpy.multiply``
+- ``method``, a string, differentiating between ``numpy.multiply(...)`` and
+ variants like ``numpy.multiply.outer``, ``numpy.multiply.accumulate``, and so
+ on. For the common case, ``numpy.multiply(...)``, ``method == '__call__'``.
+- ``inputs``, which could be a mixture of different types
+- ``kwargs``, keyword arguments passed to the function
+
+For this example we will only handle the method ``'__call__``.
+
+>>> from numbers import Number
+>>> class DiagonalArray:
+... def __init__(self, N, value):
+... self._N = N
+... self._i = value
+... def __repr__(self):
+... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
+... def __array__(self):
+... return self._i * np.eye(self._N)
+... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
+... if method == '__call__':
+... N = None
+... scalars = []
+... for input in inputs:
+... if isinstance(input, Number):
+... scalars.append(input)
+... elif isinstance(input, self.__class__):
+... scalars.append(input._i)
+... if N is not None:
+... if N != self._N:
+... raise TypeError("inconsistent sizes")
+... else:
+... N = self._N
+... else:
+... return NotImplemented
+... return self.__class__(N, ufunc(*scalars, **kwargs))
+... else:
+... return NotImplemented
+...
+
+Now our custom array type passes through numpy functions.
+
+>>> arr = DiagonalArray(5, 1)
+>>> np.multiply(arr, 3)
+DiagonalArray(N=5, value=3)
+>>> np.add(arr, 3)
+DiagonalArray(N=5, value=4)
+>>> np.sin(arr)
+DiagonalArray(N=5, value=0.8414709848078965)
+
+At this point ``arr + 3`` does not work.
+
+>>> arr + 3
+TypeError: unsupported operand type(s) for *: 'DiagonalArray' and 'int'
+
+To support it, we need to define the Python interfaces ``__add__``, ``__lt__``,
+and so on to dispatch to the corresponding ufunc. We can achieve this
+conveniently by inheriting from the mixin
+:class:`~numpy.lib.mixins.NDArrayOperatorsMixin`.
+
+>>> import numpy.lib.mixins
+>>> class DiagonalArray(numpy.lib.mixins.NDArrayOperatorsMixin):
+... def __init__(self, N, value):
+... self._N = N
+... self._i = value
+... def __repr__(self):
+... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
+... def __array__(self):
+... return self._i * np.eye(self._N)
+... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
+... if method == '__call__':
+... N = None
+... scalars = []
+... for input in inputs:
+... if isinstance(input, Number):
+... scalars.append(input)
+... elif isinstance(input, self.__class__):
+... scalars.append(input._i)
+... if N is not None:
+... if N != self._N:
+... raise TypeError("inconsistent sizes")
+... else:
+... N = self._N
+... else:
+... return NotImplemented
+... return self.__class__(N, ufunc(*scalars, **kwargs))
+... else:
+... return NotImplemented
+...
+
+>>> arr = DiagonalArray(5, 1)
+>>> arr + 3
+DiagonalArray(N=5, value=4)
+>>> arr > 0
+DiagonalArray(N=5, value=True)
+
+Now let's tackle ``__array_function__``. We'll create dict that maps numpy
+functions to our custom variants.
+
+>>> HANDLED_FUNCTIONS = {}
+>>> class DiagonalArray(numpy.lib.mixins.NDArrayOperatorsMixin):
+... def __init__(self, N, value):
+... self._N = N
+... self._i = value
+... def __repr__(self):
+... return f"{self.__class__.__name__}(N={self._N}, value={self._i})"
+... def __array__(self):
+... return self._i * np.eye(self._N)
+... def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
+... if method == '__call__':
+... N = None
+... scalars = []
+... for input in inputs:
+... # In this case we accept only scalar numbers or DiagonalArrays.
+... if isinstance(input, Number):
+... scalars.append(input)
+... elif isinstance(input, self.__class__):
+... scalars.append(input._i)
+... if N is not None:
+... if N != self._N:
+... raise TypeError("inconsistent sizes")
+... else:
+... N = self._N
+... else:
+... return NotImplemented
+... return self.__class__(N, ufunc(*scalars, **kwargs))
+... else:
+... return NotImplemented
+... def __array_function__(self, func, types, args, kwargs):
+... if func not in HANDLED_FUNCTIONS:
+... return NotImplemented
+... # Note: this allows subclasses that don't override
+... # __array_function__ to handle DiagonalArray objects.
+... if not all(issubclass(t, self.__class__) for t in types):
+... return NotImplemented
+... return HANDLED_FUNCTIONS[func](*args, **kwargs)
+...
+
+A convenient pattern is to define a decorator ``implements`` that can be used
+to add functions to ``HANDLED_FUNCTIONS``.
+
+>>> def implements(np_function):
+... "Register an __array_function__ implementation for DiagonalArray objects."
+... def decorator(func):
+... HANDLED_FUNCTIONS[np_function] = func
+... return func
+... return decorator
+...
+
+Now we write implementations of numpy functions for ``DiagonalArray``.
+For completeness, to support the usage ``arr.sum()`` add a method ``sum`` that
+calls ``numpy.sum(self)``, and the same for ``mean``.
+
+>>> @implements(np.sum)
+... def sum(a):
+... "Implementation of np.sum for DiagonalArray objects"
+... return arr._i * arr._N
+...
+>>> @implements(np.mean)
+... def sum(a):
+... "Implementation of np.mean for DiagonalArray objects"
+... return arr._i / arr._N
+...
+>>> arr = DiagonalArray(5, 1)
+>>> np.sum(arr)
+5
+>>> np.mean(arr)
+0.2
+
+If the user tries to use any numpy functions not included in
+``HANDLED_FUNCTIONS``, a ``TypeError`` will be raised by numpy, indicating that
+this operation is not supported. For example, concatenating two
+``DiagonalArrays`` does not produce another diagonal array, so it is not
+supported.
+
+>>> np.concatenate([arr, arr])
+TypeError: no implementation found for 'numpy.concatenate' on types that implement __array_function__: [<class '__main__.DiagonalArray'>]
+
+Additionally, our implementations of ``sum`` and ``mean`` do not accept the
+optional arguments that numpy's implementation does.
+
+>>> np.sum(arr, axis=0)
+TypeError: sum() got an unexpected keyword argument 'axis'
+
+The user always has the option of converting to a normal ``numpy.ndarray`` with
+:func:`numpy.asarray` and using standard numpy from there.
+
+>>> np.concatenate([np.asarray(arr), np.asarray(arr)])
+array([[1., 0., 0., 0., 0.],
+ [0., 1., 0., 0., 0.],
+ [0., 0., 1., 0., 0.],
+ [0., 0., 0., 1., 0.],
+ [0., 0., 0., 0., 1.],
+ [1., 0., 0., 0., 0.],
+ [0., 1., 0., 0., 0.],
+ [0., 0., 1., 0., 0.],
+ [0., 0., 0., 1., 0.],
+ [0., 0., 0., 0., 1.]])
+
+Refer to the `dask source code <https://github.com/dask/dask>`_ and
+`cupy source code <https://github.com/cupy/cupy>`_ for more fully-worked
+examples of custom array containers.
+
+See also `NEP 18 <http://www.numpy.org/neps/nep-0018-array-function-protocol.html>`_.
+"""
diff --git a/numpy/fft/pocketfft.py b/numpy/fft/pocketfft.py
index 45dc162f6..b7f6f1434 100644
--- a/numpy/fft/pocketfft.py
+++ b/numpy/fft/pocketfft.py
@@ -392,8 +392,9 @@ def irfft(a, n=None, axis=-1, norm=None):
Length of the transformed axis of the output.
For `n` output points, ``n//2+1`` input points are necessary. If the
input is longer than this, it is cropped. If it is shorter than this,
- it is padded with zeros. If `n` is not given, it is determined from
- the length of the input along the axis specified by `axis`.
+ it is padded with zeros. If `n` is not given, it is taken to be
+ ``2*(m-1)`` where ``m`` is the length of the input along the axis
+ specified by `axis`.
axis : int, optional
Axis over which to compute the inverse FFT. If not given, the last
axis is used.
@@ -436,6 +437,14 @@ def irfft(a, n=None, axis=-1, norm=None):
thus resample a series to `m` points via Fourier interpolation by:
``a_resamp = irfft(rfft(a), m)``.
+ The correct interpretation of the hermitian input depends on the length of
+ the original data, as given by `n`. This is because each input shape could
+ correspond to either an odd or even length signal. By default, `irfft`
+ assumes an even output length which puts the last entry at the Nyquist
+ frequency; aliasing with its symmetric counterpart. By Hermitian symmetry,
+ the value is thus treated as purely real. To avoid losing information, the
+ correct length of the real input **must** be given.
+
Examples
--------
>>> np.fft.ifft([1, -1j, -1, 1j])
@@ -473,8 +482,9 @@ def hfft(a, n=None, axis=-1, norm=None):
Length of the transformed axis of the output. For `n` output
points, ``n//2 + 1`` input points are necessary. If the input is
longer than this, it is cropped. If it is shorter than this, it is
- padded with zeros. If `n` is not given, it is determined from the
- length of the input along the axis specified by `axis`.
+ padded with zeros. If `n` is not given, it is taken to be ``2*(m-1)``
+ where ``m`` is the length of the input along the axis specified by
+ `axis`.
axis : int, optional
Axis over which to compute the FFT. If not given, the last
axis is used.
@@ -513,6 +523,14 @@ def hfft(a, n=None, axis=-1, norm=None):
* even: ``ihfft(hfft(a, 2*len(a) - 2) == a``, within roundoff error,
* odd: ``ihfft(hfft(a, 2*len(a) - 1) == a``, within roundoff error.
+ The correct interpretation of the hermitian input depends on the length of
+ the original data, as given by `n`. This is because each input shape could
+ correspond to either an odd or even length signal. By default, `hfft`
+ assumes an even output length which puts the last entry at the Nyquist
+ frequency; aliasing with its symmetric counterpart. By Hermitian symmetry,
+ the value is thus treated as purely real. To avoid losing information, the
+ shape of the full signal **must** be given.
+
Examples
--------
>>> signal = np.array([1, 2, 3, 4, 3, 2])
@@ -1167,8 +1185,9 @@ def irfftn(a, s=None, axes=None, norm=None):
where ``s[-1]//2+1`` points of the input are used.
Along any axis, if the shape indicated by `s` is smaller than that of
the input, the input is cropped. If it is larger, the input is padded
- with zeros. If `s` is not given, the shape of the input along the
- axes specified by `axes` is used.
+ with zeros. If `s` is not given, the shape of the input along the axes
+ specified by axes is used. Except for the last axis which is taken to be
+ ``2*(m-1)`` where ``m`` is the length of the input along that axis.
axes : sequence of ints, optional
Axes over which to compute the inverse FFT. If not given, the last
`len(s)` axes are used, or all axes if `s` is also not specified.
@@ -1213,6 +1232,15 @@ def irfftn(a, s=None, axes=None, norm=None):
See `rfft` for definitions and conventions used for real input.
+ The correct interpretation of the hermitian input depends on the shape of
+ the original data, as given by `s`. This is because each input shape could
+ correspond to either an odd or even length signal. By default, `irfftn`
+ assumes an even output length which puts the last entry at the Nyquist
+ frequency; aliasing with its symmetric counterpart. When performing the
+ final complex to real transform, the last value is thus treated as purely
+ real. To avoid losing information, the correct shape of the real input
+ **must** be given.
+
Examples
--------
>>> a = np.zeros((3, 2, 2))
@@ -1244,7 +1272,7 @@ def irfft2(a, s=None, axes=(-2, -1), norm=None):
a : array_like
The input array
s : sequence of ints, optional
- Shape of the inverse FFT.
+ Shape of the real output to the inverse FFT.
axes : sequence of ints, optional
The axes over which to compute the inverse fft.
Default is the last two axes.
diff --git a/numpy/polynomial/polyutils.py b/numpy/polynomial/polyutils.py
index e9fbc0fcf..a9059f522 100644
--- a/numpy/polynomial/polyutils.py
+++ b/numpy/polynomial/polyutils.py
@@ -46,6 +46,7 @@ Functions
from __future__ import division, absolute_import, print_function
import operator
+import warnings
import numpy as np
diff --git a/numpy/polynomial/tests/test_classes.py b/numpy/polynomial/tests/test_classes.py
index 15e24f92b..2261f960b 100644
--- a/numpy/polynomial/tests/test_classes.py
+++ b/numpy/polynomial/tests/test_classes.py
@@ -16,7 +16,7 @@ from numpy.testing import (
assert_almost_equal, assert_raises, assert_equal, assert_,
)
from numpy.compat import long
-
+from numpy.polynomial.polyutils import RankWarning
#
# fixtures
@@ -133,6 +133,17 @@ def test_fromroots(Poly):
assert_almost_equal(p2.coef[-1], 1)
+def test_bad_conditioned_fit(Poly):
+
+ x = [0., 0., 1.]
+ y = [1., 2., 3.]
+
+ # check RankWarning is raised
+ with pytest.warns(RankWarning) as record:
+ Poly.fit(x, y, 2)
+ assert record[0].message.args[0] == "The fit may be poorly conditioned"
+
+
def test_fit(Poly):
def f(x):
diff --git a/numpy/polynomial/tests/test_polynomial.py b/numpy/polynomial/tests/test_polynomial.py
index 08b73da15..1436963c6 100644
--- a/numpy/polynomial/tests/test_polynomial.py
+++ b/numpy/polynomial/tests/test_polynomial.py
@@ -9,7 +9,7 @@ import numpy as np
import numpy.polynomial.polynomial as poly
from numpy.testing import (
assert_almost_equal, assert_raises, assert_equal, assert_,
- assert_array_equal)
+ assert_warns, assert_array_equal)
def trim(x):
@@ -297,6 +297,8 @@ class TestIntegral(object):
assert_raises(ValueError, poly.polyint, [0], lbnd=[0])
assert_raises(ValueError, poly.polyint, [0], scl=[0])
assert_raises(TypeError, poly.polyint, [0], axis=.5)
+ with assert_warns(DeprecationWarning):
+ poly.polyint([1, 1], 1.)
# test integration of zero polynomial
for i in range(2, 5):
diff --git a/tools/test-installed-numpy.py b/tools/test-installed-numpy.py
deleted file mode 100755
index 5240253b6..000000000
--- a/tools/test-installed-numpy.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python
-from __future__ import division, absolute_import, print_function
-
-# A simple script to test the installed version of numpy by calling
-# 'numpy.test()'. Key features:
-# -- convenient command-line syntax
-# -- sets exit status appropriately, useful for automated test environments
-
-# It would be better to set this up as a module in the numpy namespace, so
-# that it could be run as:
-# python -m numpy.run_tests <args>
-# But, python2.4's -m switch only works with top-level modules, not modules
-# that are inside packages. So, once we drop 2.4 support, maybe...
-
-import sys, os
-# In case we are run from the source directory, we don't want to import numpy
-# from there, we want to import the installed version:
-sys.path.pop(0)
-
-from optparse import OptionParser
-parser = OptionParser("usage: %prog [options] -- [nosetests options]")
-parser.add_option("-v", "--verbose",
- action="count", dest="verbose", default=1,
- help="increase verbosity")
-parser.add_option("--doctests",
- action="store_true", dest="doctests", default=False,
- help="Run doctests in module")
-parser.add_option("--coverage",
- action="store_true", dest="coverage", default=False,
- help="report coverage of NumPy code (requires 'pytest-cov' module")
-parser.add_option("-m", "--mode",
- action="store", dest="mode", default="fast",
- help="'fast', 'full', or something that could be "
- "passed to pytest [default: %default]")
-parser.add_option("-n", "--durations",
- dest="durations", default=-1,
- help="show time to run slowest N tests [default: -1]")
-(options, args) = parser.parse_args()
-
-import numpy
-
-# Check that NPY_RELAXED_STRIDES_CHECKING is active when set.
-# The same flags check is also used in the tests to switch behavior.
-if (os.environ.get('NPY_RELAXED_STRIDES_CHECKING', "1") != "0"):
- if not numpy.ones((10, 1), order='C').flags.f_contiguous:
- print('NPY_RELAXED_STRIDES_CHECKING set, but not active.')
- sys.exit(1)
-elif numpy.ones((10, 1), order='C').flags.f_contiguous:
- print('NPY_RELAXED_STRIDES_CHECKING not set, but active.')
- sys.exit(1)
-
-if options.coverage:
- # Produce code coverage XML report for codecov.io
- args += ["--cov-report=xml"]
-
-result = numpy.test(options.mode,
- verbose=options.verbose,
- extra_argv=args,
- doctests=options.doctests,
- durations=int(options.durations),
- coverage=options.coverage)
-
-if result:
- sys.exit(0)
-else:
- sys.exit(1)
diff --git a/tools/travis-test.sh b/tools/travis-test.sh
index d900b88e7..77eb66b0b 100755
--- a/tools/travis-test.sh
+++ b/tools/travis-test.sh
@@ -83,9 +83,9 @@ run_test()
export PYTHONWARNINGS=default
if [ -n "$RUN_FULL_TESTS" ]; then
export PYTHONWARNINGS="ignore::DeprecationWarning:virtualenv"
- $PYTHON ../tools/test-installed-numpy.py -v --durations 10 --mode=full $COVERAGE_FLAG
+ $PYTHON ../runtests.py -n -v --durations 10 --mode=full $COVERAGE_FLAG
else
- $PYTHON ../tools/test-installed-numpy.py -v --durations 10
+ $PYTHON ../runtests.py -n -v --durations 10
fi
if [ -n "$RUN_COVERAGE" ]; then
diff --git a/tox.ini b/tox.ini
index 75229ed86..3223b9e1b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,7 +15,7 @@
# To run against a specific subset of Python versions, use:
# tox -e py37
-# Extra arguments will be passed to test-installed-numpy.py. To run
+# Extra arguments will be passed to runtests.py. To run
# the full testsuite:
# tox full
# To run with extra verbosity:
@@ -33,7 +33,7 @@ envlist =
deps=
pytest
changedir={envdir}
-commands={envpython} {toxinidir}/tools/test-installed-numpy.py --mode=full {posargs:}
+commands={envpython} {toxinidir}/runtests.py --mode=full {posargs:}
[testenv:py37-not-relaxed-strides]
basepython=python3.7
@@ -43,4 +43,4 @@ env=NPY_RELAXED_STRIDES_CHECKING=0
# if you want it:
[testenv:debug]
basepython=python-dbg
-commands=gdb --args {envpython} {toxinidir}/tools/test-installed-numpy.py --mode=full {posargs:}
+commands=gdb --args {envpython} {toxinidir}/runtests.py --mode=full {posargs:}