summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h12
-rw-r--r--numpy/core/src/multiarray/ctors.c22
-rw-r--r--numpy/core/src/multiarray/flagsobject.c56
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c19
-rw-r--r--numpy/core/src/multiarray/shape.c13
-rw-r--r--numpy/core/tests/test_api.py42
-rw-r--r--numpy/core/tests/test_multiarray.py4
-rw-r--r--numpy/core/tests/test_regression.py7
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"""