diff options
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 12 | ||||
-rw-r--r-- | numpy/core/src/multiarray/ctors.c | 22 | ||||
-rw-r--r-- | numpy/core/src/multiarray/flagsobject.c | 56 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 19 | ||||
-rw-r--r-- | numpy/core/src/multiarray/shape.c | 13 | ||||
-rw-r--r-- | numpy/core/tests/test_api.py | 42 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 4 | ||||
-rw-r--r-- | numpy/core/tests/test_regression.py | 7 |
8 files changed, 148 insertions, 27 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 05bac8a1f..7cc37bff8 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -753,9 +753,15 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *); #define NPY_ARRAY_F_CONTIGUOUS 0x0002 /* - * Note: all 0-d arrays are C_CONTIGUOUS and F_CONTIGUOUS. An N-d - * array that is C_CONTIGUOUS is also F_CONTIGUOUS if only - * one axis has a dimension different from one (ie. a 1x3x1 array). + * Note: all 0-d arrays are C_CONTIGUOUS and F_CONTIGUOUS. If a + * 1-d array is C_CONTIGUOUS it is also F_CONTIGUOUS. Arrays with + * more then one dimension can be C_CONTIGUOUS and F_CONTIGUOUS + * at the same time if they have either zero or one element. + * If NPY_RELAXED_STRIDES_CHECKING is set, a higher dimensional + * array is always C_CONTIGUOUS and F_CONTIGUOUS if it has zero elements + * and the array is contiguous if ndarray.squeeze() is contiguous. + * I.e. dimensions for which `ndarray.shape[dimension] == 1` are + * ignored. */ /* diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index bdf2e6e2b..f366a34b1 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -3560,6 +3560,7 @@ _array_fill_strides(npy_intp *strides, npy_intp *dims, int nd, size_t itemsize, int inflag, int *objflags) { int i; +#if NPY_RELAXED_STRIDES_CHECKING npy_bool not_cf_contig = 0; npy_bool nod = 0; /* A dim != 1 was found */ @@ -3573,6 +3574,7 @@ _array_fill_strides(npy_intp *strides, npy_intp *dims, int nd, size_t itemsize, nod = 1; } } +#endif /* NPY_RELAXED_STRIDES_CHECKING */ /* Only make Fortran strides if not contiguous as well */ if ((inflag & (NPY_ARRAY_F_CONTIGUOUS|NPY_ARRAY_C_CONTIGUOUS)) == @@ -3582,11 +3584,21 @@ _array_fill_strides(npy_intp *strides, npy_intp *dims, int nd, size_t itemsize, if (dims[i]) { itemsize *= dims[i]; } +#if NPY_RELAXED_STRIDES_CHECKING else { not_cf_contig = 0; } + if (dims[i] == 1) { + /* For testing purpose only */ + strides[i] = NPY_MAX_INTP; + } +#endif /* NPY_RELAXED_STRIDES_CHECKING */ } +#if NPY_RELAXED_STRIDES_CHECKING if (not_cf_contig) { +#else /* not NPY_RELAXED_STRIDES_CHECKING */ + if ((nd > 1) && ((strides[0] != strides[nd-1]) || (dims[nd-1] > 1))) { +#endif /* not NPY_RELAXED_STRIDES_CHECKING */ *objflags = ((*objflags)|NPY_ARRAY_F_CONTIGUOUS) & ~NPY_ARRAY_C_CONTIGUOUS; } @@ -3600,11 +3612,21 @@ _array_fill_strides(npy_intp *strides, npy_intp *dims, int nd, size_t itemsize, if (dims[i]) { itemsize *= dims[i]; } +#if NPY_RELAXED_STRIDES_CHECKING else { not_cf_contig = 0; } + if (dims[i] == 1) { + /* For testing purpose only */ + strides[i] = NPY_MAX_INTP; + } +#endif /* NPY_RELAXED_STRIDES_CHECKING */ } +#if NPY_RELAXED_STRIDES_CHECKING if (not_cf_contig) { +#else /* not NPY_RELAXED_STRIDES_CHECKING */ + if ((nd > 1) && ((strides[0] != strides[nd-1]) || (dims[0] > 1))) { +#endif /* not NPY_RELAXED_STRIDES_CHECKING */ *objflags = ((*objflags)|NPY_ARRAY_C_CONTIGUOUS) & ~NPY_ARRAY_F_CONTIGUOUS; } diff --git a/numpy/core/src/multiarray/flagsobject.c b/numpy/core/src/multiarray/flagsobject.c index ef04bdb20..0ad5c908a 100644 --- a/numpy/core/src/multiarray/flagsobject.c +++ b/numpy/core/src/multiarray/flagsobject.c @@ -90,8 +90,33 @@ PyArray_UpdateFlags(PyArrayObject *ret, int flagmask) * Check whether the given array is stored contiguously * in memory. And update the passed in ap flags apropriately. * - * A dimension == 1 stride is ignored for contiguous flags and a 0-sized array - * is always both C- and F-Contiguous. 0-strided arrays are not contiguous. + * The traditional rule is that for an array to be flagged as C contiguous, + * the following must hold: + * + * strides[-1] == itemsize + * strides[i] == shape[i+1] * strides[i + 1] + * + * And for an array to be flagged as F contiguous, the obvious reversal: + * + * strides[0] == itemsize + * strides[i] == shape[i - 1] * strides[i - 1] + * + * According to these rules, a 0- or 1-dimensional array is either both + * C- and F-contiguous, or neither; and an array with 2+ dimensions + * can be C- or F- contiguous, or neither, but not both. Though there + * there are exceptions for arrays with zero or one item, in the first + * case the check is relaxed up to and including the first dimension + * with shape[i] == 0. In the second case `strides == itemsize` will + * can be true for all dimensions and both flags are set. + * + * When NPY_RELAXED_STRIDES_CHECKING is set, we use a more accurate + * definition of C- and F-contiguity, in which all 0-sized arrays are + * contiguous (regardless of dimensionality), and if shape[i] == 1 + * then we ignore strides[i] (since it has no affect on memory layout). + * With these new rules, it is possible for e.g. a 10x1 array to be both + * C- and F-contiguous -- but, they break downstream code which assumes + * that for contiguous arrays strides[-1] (resp. strides[0]) always + * contains the itemsize. */ static void _UpdateContiguousFlags(PyArrayObject *ap) @@ -101,9 +126,10 @@ _UpdateContiguousFlags(PyArrayObject *ap) int i; npy_bool is_c_contig = 1; - sd = PyArray_DESCR(ap)->elsize; + sd = PyArray_ITEMSIZE(ap); for (i = PyArray_NDIM(ap) - 1; i >= 0; --i) { dim = PyArray_DIMS(ap)[i]; +#if NPY_RELAXED_STRIDES_CHECKING /* contiguous by definition */ if (dim == 0) { PyArray_ENABLEFLAGS(ap, NPY_ARRAY_C_CONTIGUOUS); @@ -116,6 +142,17 @@ _UpdateContiguousFlags(PyArrayObject *ap) } sd *= dim; } +#else /* not NPY_RELAXED_STRIDES_CHECKING */ + if (PyArray_STRIDES(ap)[i] != sd) { + is_c_contig = 0; + break; + } + /* contiguous, if it got this far */ + if (dim == 0) { + break; + } + sd *= dim; +#endif /* not NPY_RELAXED_STRIDES_CHECKING */ } if (is_c_contig) { PyArray_ENABLEFLAGS(ap, NPY_ARRAY_C_CONTIGUOUS); @@ -125,9 +162,10 @@ _UpdateContiguousFlags(PyArrayObject *ap) } /* check if fortran contiguous */ - sd = PyArray_DESCR(ap)->elsize; + sd = PyArray_ITEMSIZE(ap); for (i = 0; i < PyArray_NDIM(ap); ++i) { dim = PyArray_DIMS(ap)[i]; +#if NPY_RELAXED_STRIDES_CHECKING if (dim != 1) { if (PyArray_STRIDES(ap)[i] != sd) { PyArray_CLEARFLAGS(ap, NPY_ARRAY_F_CONTIGUOUS); @@ -135,6 +173,16 @@ _UpdateContiguousFlags(PyArrayObject *ap) } sd *= dim; } +#else /* not NPY_RELAXED_STRIDES_CHECKING */ + if (PyArray_STRIDES(ap)[i] != sd) { + PyArray_CLEARFLAGS(ap, NPY_ARRAY_F_CONTIGUOUS); + return; + } + if (dim == 0) { + break; + } + sd *= dim; +#endif /* not NPY_RELAXED_STRIDES_CHECKING */ } PyArray_ENABLEFLAGS(ap, NPY_ARRAY_F_CONTIGUOUS); return; diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index f8ade57da..dd6d44003 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -1510,20 +1510,31 @@ PyArray_EquivTypenums(int typenum1, int typenum2) } /*** END C-API FUNCTIONS **/ - +/* + * NPY_RELAXED_STRIDES_CHECKING: If the strides logic is changed, the + * order specific stride setting is not necessary. + */ static PyObject * -_prepend_ones(PyArrayObject *arr, int nd, int ndmin) +_prepend_ones(PyArrayObject *arr, int nd, int ndmin, NPY_ORDER order) { npy_intp newdims[NPY_MAXDIMS]; npy_intp newstrides[NPY_MAXDIMS]; + npy_intp newstride; int i, k, num; PyArrayObject *ret; PyArray_Descr *dtype; + if (order == NPY_FORTRANORDER || PyArray_ISFORTRAN(arr) || PyArray_NDIM(arr) == 0) { + newstride = PyArray_DESCR(arr)->elsize; + } + else { + newstride = PyArray_STRIDES(arr)[0] * PyArray_DIMS(arr)[0]; + } + num = ndmin - nd; for (i = 0; i < num; i++) { newdims[i] = 1; - newstrides[i] = PyArray_DESCR(arr)->elsize; + newstrides[i] = newstride; } for (i = num; i < ndmin; i++) { k = i - num; @@ -1664,7 +1675,7 @@ _array_fromobject(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kws) * create a new array from the same data with ones in the shape * steals a reference to ret */ - return _prepend_ones(ret, nd, ndmin); + return _prepend_ones(ret, nd, ndmin, order); clean_type: Py_XDECREF(type); diff --git a/numpy/core/src/multiarray/shape.c b/numpy/core/src/multiarray/shape.c index 4223e49f6..67ee4b04b 100644 --- a/numpy/core/src/multiarray/shape.c +++ b/numpy/core/src/multiarray/shape.c @@ -214,9 +214,11 @@ PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims, * in order to get the right orientation and * because we can't just re-use the buffer with the * data in the order it is in. + * NPY_RELAXED_STRIDES_CHECKING: size check is unnecessary when set. */ - if ((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(self)) || - (order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(self))) { + if ((PyArray_SIZE(self) > 1) && + ((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(self)) || + (order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(self)))) { int success = 0; success = _attempt_nocopy_reshape(self, ndim, dimensions, newstrides, order); @@ -1102,7 +1104,9 @@ build_shape_string(npy_intp n, npy_intp *vals) * the array will point to invalid memory. The caller must * validate this! * If an axis flagged for removal has a shape larger then one, - * the arrays contiguous flags may require updating. + * the aligned flag (and in the future the contiguous flags), + * may need explicite update. + * (check also NPY_RELAXED_STRIDES_CHECKING) * * For example, this can be used to remove the reduction axes * from a reduction result once its computation is complete. @@ -1125,4 +1129,7 @@ PyArray_RemoveAxesInPlace(PyArrayObject *arr, npy_bool *flags) /* The final number of dimensions */ fa->nd = idim_out; + + /* May not be necessary for NPY_RELAXED_STRIDES_CHECKING (see comment) */ + PyArray_UpdateFlags(arr, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS); } diff --git a/numpy/core/tests/test_api.py b/numpy/core/tests/test_api.py index 484b6afbd..e919e0ae4 100644 --- a/numpy/core/tests/test_api.py +++ b/numpy/core/tests/test_api.py @@ -7,6 +7,9 @@ from numpy.testing import * from numpy.testing.utils import WarningManager import warnings +# Switch between new behaviour when NPY_RELAXED_STRIDES_CHECKING is set. +NPY_RELAXED_STRIDES_CHECKING = np.ones((10,1), order='C').flags.f_contiguous + def test_fastCopyAndTranspose(): # 0D array a = np.array(2) @@ -149,10 +152,13 @@ def test_copy_order(): assert_equal(x, y) assert_equal(res.flags.c_contiguous, ccontig) assert_equal(res.flags.f_contiguous, fcontig) - if strides: - assert_equal(x.strides, y.strides) - else: - assert_(x.strides != y.strides) + # This check is impossible only because + # NPY_RELAXED_STRIDES_CHECKING changes the strides actively + if not NPY_RELAXED_STRIDES_CHECKING: + if strides: + assert_equal(x.strides, y.strides) + else: + assert_(x.strides != y.strides) # Validate the initial state of a, b, and c assert_(a.flags.c_contiguous) @@ -206,7 +212,8 @@ def test_copy_order(): def test_contiguous_flags(): a = np.ones((4,4,1))[::2,:,:] - a.strides = a.strides[:2] + (-123,) + if NPY_RELAXED_STRIDES_CHECKING: + a.strides = a.strides[:2] + (-123,) b = np.ones((2,2,1,2,2)).swapaxes(3,4) def check_contig(a, ccontig, fcontig): @@ -216,8 +223,12 @@ def test_contiguous_flags(): # Check if new arrays are correct: check_contig(a, False, False) check_contig(b, False, False) - check_contig(np.empty((2,2,0,2,2)), True, True) - check_contig(np.array([[[1],[2]]], order='F'), True, True) + if NPY_RELAXED_STRIDES_CHECKING: + check_contig(np.empty((2,2,0,2,2)), True, True) + check_contig(np.array([[[1],[2]]], order='F'), True, True) + else: + check_contig(np.empty((2,2,0,2,2)), True, False) + check_contig(np.array([[[1],[2]]], order='F'), False, True) check_contig(np.empty((2,2)), True, False) check_contig(np.empty((2,2), order='F'), False, True) @@ -226,11 +237,18 @@ def test_contiguous_flags(): check_contig(np.array(a, copy=False, order='C'), True, False) check_contig(np.array(a, ndmin=4, copy=False, order='F'), False, True) - # Check slicing update of flags and : - check_contig(a[0], True, True) - check_contig(a[None,::4,...,None], True, True) - check_contig(b[0,0,...], False, True) - check_contig(b[:,:,0:0,:,:], True, True) + if NPY_RELAXED_STRIDES_CHECKING: + # Check slicing update of flags and : + check_contig(a[0], True, True) + check_contig(a[None,::4,...,None], True, True) + check_contig(b[0,0,...], False, True) + check_contig(b[:,:,0:0,:,:], True, True) + else: + # Check slicing update of flags: + check_contig(a[0], True, False) + # Would be nice if this was C-Contiguous: + check_contig(a[None,0,...,None], False, False) + check_contig(b[0,0,0,...], False, True) # Test ravel and squeeze. check_contig(a.ravel(), True, True) diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 25cc8ced8..b457c4773 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -2764,7 +2764,9 @@ if sys.version_info >= (2, 6): assert_equal(y.format, 'T{b:a:=h:b:i:c:l:d:^q:dx:B:e:@H:f:=I:g:L:h:^Q:hx:=f:i:d:j:^g:k:=Zf:ix:Zd:jx:^Zg:kx:4s:l:=4w:m:3x:n:?:o:@e:p:}') else: assert_equal(y.format, 'T{b:a:=h:b:i:c:q:d:^q:dx:B:e:@H:f:=I:g:Q:h:^Q:hx:=f:i:d:j:^g:k:=Zf:ix:Zd:jx:^Zg:kx:4s:l:=4w:m:3x:n:?:o:@e:p:}') - assert_equal(y.strides, (sz,)) + # Cannot test if NPY_RELAXED_STRIDES_CHECKING changes the strides + if not (np.ones(1).strides[0] == np.iinfo(np.intp).max): + assert_equal(y.strides, (sz,)) assert_equal(y.itemsize, sz) def test_export_subarray(self): diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 95df4a113..e87df62b7 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -547,6 +547,9 @@ class TestRegression(TestCase): a = np.ones((0,2)) a.shape = (-1,2) + # Cannot test if NPY_RELAXED_STRIDES_CHECKING changes the strides. + # With NPY_RELAXED_STRIDES_CHECKING the test becomes superfluous. + @dec.skipif(np.ones(1).strides[0] == np.iinfo(np.intp).max) def test_reshape_trailing_ones_strides(self): # Github issue gh-2949, bad strides for trailing ones of new shape a = np.zeros(12, dtype=np.int32)[::2] # not contiguous @@ -799,6 +802,10 @@ class TestRegression(TestCase): """Ticket #658""" np.indices((0,3,4)).T.reshape(-1,3) + # Cannot test if NPY_RELAXED_STRIDES_CHECKING changes the strides. + # With NPY_RELAXED_STRIDES_CHECKING the test becomes superfluous, + # 0-sized reshape itself is tested elsewhere. + @dec.skipif(np.ones(1).strides[0] == np.iinfo(np.intp).max) def test_copy_detection_corner_case2(self, level=rlevel): """Ticket #771: strides are not set correctly when reshaping 0-sized arrays""" |