You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
8.9 KiB
Python
269 lines
8.9 KiB
Python
"""
|
|
Utilities that manipulate strides to achieve desirable effects.
|
|
|
|
An explanation of strides can be found in the "ndarray.rst" file in the
|
|
NumPy reference guide.
|
|
|
|
"""
|
|
from __future__ import division, absolute_import, print_function
|
|
|
|
import numpy as np
|
|
from numpy.core.overrides import array_function_dispatch
|
|
|
|
__all__ = ['broadcast_to', 'broadcast_arrays']
|
|
|
|
|
|
class DummyArray(object):
|
|
"""Dummy object that just exists to hang __array_interface__ dictionaries
|
|
and possibly keep alive a reference to a base array.
|
|
"""
|
|
|
|
def __init__(self, interface, base=None):
|
|
self.__array_interface__ = interface
|
|
self.base = base
|
|
|
|
|
|
def _maybe_view_as_subclass(original_array, new_array):
|
|
if type(original_array) is not type(new_array):
|
|
# if input was an ndarray subclass and subclasses were OK,
|
|
# then view the result as that subclass.
|
|
new_array = new_array.view(type=type(original_array))
|
|
# Since we have done something akin to a view from original_array, we
|
|
# should let the subclass finalize (if it has it implemented, i.e., is
|
|
# not None).
|
|
if new_array.__array_finalize__:
|
|
new_array.__array_finalize__(original_array)
|
|
return new_array
|
|
|
|
|
|
def as_strided(x, shape=None, strides=None, subok=False, writeable=True):
|
|
"""
|
|
Create a view into the array with the given shape and strides.
|
|
|
|
.. warning:: This function has to be used with extreme care, see notes.
|
|
|
|
Parameters
|
|
----------
|
|
x : ndarray
|
|
Array to create a new.
|
|
shape : sequence of int, optional
|
|
The shape of the new array. Defaults to ``x.shape``.
|
|
strides : sequence of int, optional
|
|
The strides of the new array. Defaults to ``x.strides``.
|
|
subok : bool, optional
|
|
.. versionadded:: 1.10
|
|
|
|
If True, subclasses are preserved.
|
|
writeable : bool, optional
|
|
.. versionadded:: 1.12
|
|
|
|
If set to False, the returned array will always be readonly.
|
|
Otherwise it will be writable if the original array was. It
|
|
is advisable to set this to False if possible (see Notes).
|
|
|
|
Returns
|
|
-------
|
|
view : ndarray
|
|
|
|
See also
|
|
--------
|
|
broadcast_to: broadcast an array to a given shape.
|
|
reshape : reshape an array.
|
|
|
|
Notes
|
|
-----
|
|
``as_strided`` creates a view into the array given the exact strides
|
|
and shape. This means it manipulates the internal data structure of
|
|
ndarray and, if done incorrectly, the array elements can point to
|
|
invalid memory and can corrupt results or crash your program.
|
|
It is advisable to always use the original ``x.strides`` when
|
|
calculating new strides to avoid reliance on a contiguous memory
|
|
layout.
|
|
|
|
Furthermore, arrays created with this function often contain self
|
|
overlapping memory, so that two elements are identical.
|
|
Vectorized write operations on such arrays will typically be
|
|
unpredictable. They may even give different results for small, large,
|
|
or transposed arrays.
|
|
Since writing to these arrays has to be tested and done with great
|
|
care, you may want to use ``writeable=False`` to avoid accidental write
|
|
operations.
|
|
|
|
For these reasons it is advisable to avoid ``as_strided`` when
|
|
possible.
|
|
"""
|
|
# first convert input to array, possibly keeping subclass
|
|
x = np.array(x, copy=False, subok=subok)
|
|
interface = dict(x.__array_interface__)
|
|
if shape is not None:
|
|
interface['shape'] = tuple(shape)
|
|
if strides is not None:
|
|
interface['strides'] = tuple(strides)
|
|
|
|
array = np.asarray(DummyArray(interface, base=x))
|
|
# The route via `__interface__` does not preserve structured
|
|
# dtypes. Since dtype should remain unchanged, we set it explicitly.
|
|
array.dtype = x.dtype
|
|
|
|
view = _maybe_view_as_subclass(x, array)
|
|
|
|
if view.flags.writeable and not writeable:
|
|
view.flags.writeable = False
|
|
|
|
return view
|
|
|
|
|
|
def _broadcast_to(array, shape, subok, readonly):
|
|
shape = tuple(shape) if np.iterable(shape) else (shape,)
|
|
array = np.array(array, copy=False, subok=subok)
|
|
if not shape and array.shape:
|
|
raise ValueError('cannot broadcast a non-scalar to a scalar array')
|
|
if any(size < 0 for size in shape):
|
|
raise ValueError('all elements of broadcast shape must be non-'
|
|
'negative')
|
|
needs_writeable = not readonly and array.flags.writeable
|
|
extras = ['reduce_ok'] if needs_writeable else []
|
|
op_flag = 'readwrite' if needs_writeable else 'readonly'
|
|
it = np.nditer(
|
|
(array,), flags=['multi_index', 'refs_ok', 'zerosize_ok'] + extras,
|
|
op_flags=[op_flag], itershape=shape, order='C')
|
|
with it:
|
|
# never really has writebackifcopy semantics
|
|
broadcast = it.itviews[0]
|
|
result = _maybe_view_as_subclass(array, broadcast)
|
|
if needs_writeable and not result.flags.writeable:
|
|
result.flags.writeable = True
|
|
return result
|
|
|
|
|
|
def _broadcast_to_dispatcher(array, shape, subok=None):
|
|
return (array,)
|
|
|
|
|
|
@array_function_dispatch(_broadcast_to_dispatcher, module='numpy')
|
|
def broadcast_to(array, shape, subok=False):
|
|
"""Broadcast an array to a new shape.
|
|
|
|
Parameters
|
|
----------
|
|
array : array_like
|
|
The array to broadcast.
|
|
shape : tuple
|
|
The shape of the desired array.
|
|
subok : bool, optional
|
|
If True, then sub-classes will be passed-through, otherwise
|
|
the returned array will be forced to be a base-class array (default).
|
|
|
|
Returns
|
|
-------
|
|
broadcast : array
|
|
A readonly view on the original array with the given shape. It is
|
|
typically not contiguous. Furthermore, more than one element of a
|
|
broadcasted array may refer to a single memory location.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If the array is not compatible with the new shape according to NumPy's
|
|
broadcasting rules.
|
|
|
|
Notes
|
|
-----
|
|
.. versionadded:: 1.10.0
|
|
|
|
Examples
|
|
--------
|
|
>>> x = np.array([1, 2, 3])
|
|
>>> np.broadcast_to(x, (3, 3))
|
|
array([[1, 2, 3],
|
|
[1, 2, 3],
|
|
[1, 2, 3]])
|
|
"""
|
|
return _broadcast_to(array, shape, subok=subok, readonly=True)
|
|
|
|
|
|
def _broadcast_shape(*args):
|
|
"""Returns the shape of the arrays that would result from broadcasting the
|
|
supplied arrays against each other.
|
|
"""
|
|
if not args:
|
|
return ()
|
|
# use the old-iterator because np.nditer does not handle size 0 arrays
|
|
# consistently
|
|
b = np.broadcast(*args[:32])
|
|
# unfortunately, it cannot handle 32 or more arguments directly
|
|
for pos in range(32, len(args), 31):
|
|
# ironically, np.broadcast does not properly handle np.broadcast
|
|
# objects (it treats them as scalars)
|
|
# use broadcasting to avoid allocating the full array
|
|
b = broadcast_to(0, b.shape)
|
|
b = np.broadcast(b, *args[pos:(pos + 31)])
|
|
return b.shape
|
|
|
|
|
|
def _broadcast_arrays_dispatcher(*args, **kwargs):
|
|
return args
|
|
|
|
|
|
@array_function_dispatch(_broadcast_arrays_dispatcher, module='numpy')
|
|
def broadcast_arrays(*args, **kwargs):
|
|
"""
|
|
Broadcast any number of arrays against each other.
|
|
|
|
Parameters
|
|
----------
|
|
`*args` : array_likes
|
|
The arrays to broadcast.
|
|
|
|
subok : bool, optional
|
|
If True, then sub-classes will be passed-through, otherwise
|
|
the returned arrays will be forced to be a base-class array (default).
|
|
|
|
Returns
|
|
-------
|
|
broadcasted : list of arrays
|
|
These arrays are views on the original arrays. They are typically
|
|
not contiguous. Furthermore, more than one element of a
|
|
broadcasted array may refer to a single memory location. If you
|
|
need to write to the arrays, make copies first.
|
|
|
|
Examples
|
|
--------
|
|
>>> x = np.array([[1,2,3]])
|
|
>>> y = np.array([[4],[5]])
|
|
>>> np.broadcast_arrays(x, y)
|
|
[array([[1, 2, 3],
|
|
[1, 2, 3]]), array([[4, 4, 4],
|
|
[5, 5, 5]])]
|
|
|
|
Here is a useful idiom for getting contiguous copies instead of
|
|
non-contiguous views.
|
|
|
|
>>> [np.array(a) for a in np.broadcast_arrays(x, y)]
|
|
[array([[1, 2, 3],
|
|
[1, 2, 3]]), array([[4, 4, 4],
|
|
[5, 5, 5]])]
|
|
|
|
"""
|
|
# nditer is not used here to avoid the limit of 32 arrays.
|
|
# Otherwise, something like the following one-liner would suffice:
|
|
# return np.nditer(args, flags=['multi_index', 'zerosize_ok'],
|
|
# order='C').itviews
|
|
|
|
subok = kwargs.pop('subok', False)
|
|
if kwargs:
|
|
raise TypeError('broadcast_arrays() got an unexpected keyword '
|
|
'argument {!r}'.format(list(kwargs.keys())[0]))
|
|
args = [np.array(_m, copy=False, subok=subok) for _m in args]
|
|
|
|
shape = _broadcast_shape(*args)
|
|
|
|
if all(array.shape == shape for array in args):
|
|
# Common case where nothing needs to be broadcasted.
|
|
return args
|
|
|
|
# TODO: consider making the results of broadcast_arrays readonly to match
|
|
# broadcast_to. This will require a deprecation cycle.
|
|
return [_broadcast_to(array, shape, subok=subok, readonly=False)
|
|
for array in args]
|