Skip to content

BUG/MAINT/ENH: Allow viewing where only the last axis is contiguous #9497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/release/1.14.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ By default, ``np.einsum`` will also attempt optimization as the overhead is
small relative to the potential improvement in speed.


``.view`` now works on arrays where only the last axis is contiguous
--------------------------------------------------------------------
Previously it only worked on C-contiguous arrays when the new dtype was a
different size.


Changes
=======

Expand Down
113 changes: 62 additions & 51 deletions numpy/core/src/multiarray/getset.c
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,7 @@ array_descr_set(PyArrayObject *self, PyObject *arg)
{
PyArray_Descr *newtype = NULL;
npy_intp newdim;
int i;
char *msg = "new type not compatible with array.";
int axis;
PyObject *safe;
static PyObject *checkfunc = NULL;

Expand All @@ -461,14 +460,13 @@ array_descr_set(PyArrayObject *self, PyObject *arg)
if (_may_have_objects(PyArray_DESCR(self)) || _may_have_objects(newtype)) {
npy_cache_import("numpy.core._internal", "_view_is_safe", &checkfunc);
if (checkfunc == NULL) {
return -1;
goto fail;
}

safe = PyObject_CallFunction(checkfunc, "OO",
PyArray_DESCR(self), newtype);
if (safe == NULL) {
Py_DECREF(newtype);
return -1;
goto fail;
}
Py_DECREF(safe);
}
Expand All @@ -492,58 +490,71 @@ array_descr_set(PyArrayObject *self, PyObject *arg)
}


if ((newtype->elsize != PyArray_DESCR(self)->elsize) &&
(PyArray_NDIM(self) == 0 ||
!PyArray_ISONESEGMENT(self) ||
PyDataType_HASSUBARRAY(newtype))) {
goto fail;
}
/* Changing the size of the dtype results in a shape change */
if (newtype->elsize != PyArray_DESCR(self)->elsize) {
/* forbidden cases */
if (PyArray_NDIM(self) == 0) {
PyErr_SetString(PyExc_ValueError,
"Changing the dtype of a 0d array is only supported "
"if the itemsize is unchanged");
goto fail;
}
else if (PyDataType_HASSUBARRAY(newtype)) {
PyErr_SetString(PyExc_ValueError,
"Changing the dtype to a subarray type is only supported "
"if the total itemsize is unchanged");
goto fail;
}

/* Deprecate not C contiguous and a dimension changes */
if (newtype->elsize != PyArray_DESCR(self)->elsize &&
!PyArray_IS_C_CONTIGUOUS(self)) {
/* 11/27/2015 1.11.0 */
if (DEPRECATE("Changing the shape of non-C contiguous array by\n"
"descriptor assignment is deprecated. To maintain\n"
"the Fortran contiguity of a multidimensional Fortran\n"
"array, use 'a.T.view(...).T' instead") < 0) {
return -1;
/* determine which axis to resize */
if (PyArray_IS_F_CONTIGUOUS(self) &&
!PyArray_IS_C_CONTIGUOUS(self)) {
/* 2015-11-27 1.11.0 */
if (DEPRECATE("Changing the shape of an F-contiguous array by "
"descriptor assignment is deprecated. To maintain "
"the Fortran contiguity of a multidimensional Fortran "
"array, use 'a.T.view(...).T' instead") < 0) {
goto fail;
}
axis = 0;
}
else {
axis = PyArray_NDIM(self) - 1;
if (PyArray_STRIDES(self)[axis] != PyArray_DESCR(self)->elsize) {
PyErr_SetString(PyExc_ValueError,
"To change the size of the dtype, the array must be "
"contiguous in the last dimension");
goto fail;
}
}
}

if (PyArray_IS_C_CONTIGUOUS(self)) {
i = PyArray_NDIM(self) - 1;
}
else {
i = 0;
}
if (newtype->elsize < PyArray_DESCR(self)->elsize) {
/*
* if it is compatible increase the size of the
* dimension at end (or at the front for NPY_ARRAY_F_CONTIGUOUS)
*/
if (PyArray_DESCR(self)->elsize % newtype->elsize != 0) {
goto fail;
if (newtype->elsize < PyArray_DESCR(self)->elsize) {
/* if it is compatible, increase the size of the relevant axis */
if (PyArray_DESCR(self)->elsize % newtype->elsize != 0) {
PyErr_SetString(PyExc_ValueError,
"When changing to a smaller dtype, its size must be a "
"divisor of the size of original dtype");
goto fail;
}
newdim = PyArray_DESCR(self)->elsize / newtype->elsize;
PyArray_DIMS(self)[axis] *= newdim;
PyArray_STRIDES(self)[axis] = newtype->elsize;
}
newdim = PyArray_DESCR(self)->elsize / newtype->elsize;
PyArray_DIMS(self)[i] *= newdim;
PyArray_STRIDES(self)[i] = newtype->elsize;
}
else if (newtype->elsize > PyArray_DESCR(self)->elsize) {
/*
* Determine if last (or first if NPY_ARRAY_F_CONTIGUOUS) dimension
* is compatible
*/
newdim = PyArray_DIMS(self)[i] * PyArray_DESCR(self)->elsize;
if ((newdim % newtype->elsize) != 0) {
goto fail;
else if (newtype->elsize > PyArray_DESCR(self)->elsize) {
/* if it is compatible, decrease the size of the relevant axis */
newdim = PyArray_DIMS(self)[axis] * PyArray_DESCR(self)->elsize;
if ((newdim % newtype->elsize) != 0) {
PyErr_SetString(PyExc_ValueError,
"When changing to a larger dtype, its size must be a "
"multiple of the size of original dtype");
goto fail;
}
PyArray_DIMS(self)[axis] = newdim / newtype->elsize;
PyArray_STRIDES(self)[axis] = newtype->elsize;
}
PyArray_DIMS(self)[i] = newdim / newtype->elsize;
PyArray_STRIDES(self)[i] = newtype->elsize;
}

/* fall through -- adjust type*/
Py_DECREF(PyArray_DESCR(self));
/* Viewing as a subarray increases the number of dimensions */
if (PyDataType_HASSUBARRAY(newtype)) {
/*
* create new array object from data and update
Expand Down Expand Up @@ -573,12 +584,12 @@ array_descr_set(PyArrayObject *self, PyObject *arg)
Py_DECREF(temp);
}

Py_DECREF(PyArray_DESCR(self));
((PyArrayObject_fields *)self)->descr = newtype;
PyArray_UpdateFlags(self, NPY_ARRAY_UPDATE_ALL);
return 0;

fail:
PyErr_SetString(PyExc_ValueError, msg);
Py_DECREF(newtype);
return -1;
}
Expand Down
18 changes: 18 additions & 0 deletions numpy/ma/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4434,6 +4434,24 @@ def test_view(self):
assert_equal(test, data)
assert_(isinstance(test, np.matrix))

def test_view_last_contiguous(self):
# gh-9496
arr = np.arange(24, dtype=np.float64).reshape(3, 4, 2)
arr2 = arr[:,::2,:]
arr_c = arr2.view(np.complex128)
assert_equal(arr_c,
np.array([[
[ 0 +1j],
[ 4 +5j]],

[[ 8 +9j],
[ 12+13j]],

[[ 16+17j],
[ 20+21j]]
])
)

def test_getitem(self):
ndtype = [('a', float), ('b', float)]
a = array(list(zip(np.random.rand(10), np.arange(10))), dtype=ndtype)
Expand Down