Buffers: Added C and Fortran contiguous modes
authorDag Sverre Seljebotn <dagss@student.matnat.uio.no>
Wed, 17 Sep 2008 09:56:30 +0000 (11:56 +0200)
committerDag Sverre Seljebotn <dagss@student.matnat.uio.no>
Wed, 17 Sep 2008 09:56:30 +0000 (11:56 +0200)
Cython/Compiler/Buffer.py
Cython/Includes/numpy.pxd
tests/run/bufaccess.pyx
tests/run/numpy_test.pyx

index 985351ed8047ada944b6a87d318e5649aa5caef0..4843bce13b23b756912768a0ac1144c9e8c474e6 100644 (file)
@@ -92,7 +92,7 @@ class IntroduceBufferAuxiliaryVars(CythonTransform):
             mode = entry.type.mode
             if mode == 'full':
                 suboffsetvars = [var(Naming.bufsuboffset_prefix, i, "-1") for i in range(entry.type.ndim)]
-            elif mode == 'strided':
+            else:
                 suboffsetvars = None
 
             entry.buffer_aux = Symtab.BufferAux(bufinfo, stridevars, shapevars, suboffsetvars)
@@ -121,7 +121,7 @@ ERR_BUF_OPTION_UNKNOWN = '"%s" is not a buffer option'
 ERR_BUF_TOO_MANY = 'Too many buffer options'
 ERR_BUF_DUP = '"%s" buffer option already supplied'
 ERR_BUF_MISSING = '"%s" missing'
-ERR_BUF_MODE = 'Only allowed buffer modes are "full" or "strided" (as a compile-time string)'
+ERR_BUF_MODE = 'Only allowed buffer modes are: "c", "fortran", "full", "strided" (as a compile-time string)'
 ERR_BUF_NDIM = 'ndim must be a non-negative integer'
 ERR_BUF_DTYPE = 'dtype must be "object", numeric type or a struct'
 
@@ -175,7 +175,7 @@ def analyse_buffer_options(globalpos, env, posargs, dictargs, defaults=None, nee
         raise CompileError(globalpos, ERR_BUF_NDIM)
 
     mode = options.get("mode")
-    if mode and not (mode in ('full', 'strided')):
+    if mode and not (mode in ('full', 'strided', 'c', 'fortran')):
         raise CompileError(globalpos, ERR_BUF_MODE)
 
     return options
@@ -188,10 +188,15 @@ def analyse_buffer_options(globalpos, env, posargs, dictargs, defaults=None, nee
 
 def get_flags(buffer_aux, buffer_type):
     flags = 'PyBUF_FORMAT'
-    if buffer_type.mode == 'full':
+    mode = buffer_type.mode
+    if mode == 'full':
         flags += '| PyBUF_INDIRECT'
-    elif buffer_type.mode == 'strided':
+    elif mode == 'strided':
         flags += '| PyBUF_STRIDES'
+    elif mode == 'c':
+        flags += '| PyBUF_C_CONTIGUOUS'
+    elif mode == 'fortran':
+        flags += '| PyBUF_F_CONTIGUOUS'
     else:
         assert False
     if buffer_aux.writable_needed: flags += "| PyBUF_WRITABLE"
@@ -367,28 +372,43 @@ def put_buffer_lookup_code(entry, index_signeds, index_cnames, options, pos, cod
                 code.putln("if (%s < 0) %s += %s;" % (cname, cname, shape.cname))
         
     # Create buffer lookup and return it
+    # This is done via utility macros/inline functions, which vary
+    # according to the access mode used.
     params = []
     nd = entry.type.ndim
-    if entry.type.mode == 'full':
+    mode = entry.type.mode
+    if mode == 'full':
         for i, s, o in zip(index_cnames, bufaux.stridevars, bufaux.suboffsetvars):
             params.append(i)
             params.append(s.cname)
             params.append(o.cname)
-
         funcname = "__Pyx_BufPtrFull%dd" % nd
         funcgen = buf_lookup_full_code
     else:
+        if mode == 'strided':
+            funcname = "__Pyx_BufPtrStrided%dd" % nd
+            funcgen = buf_lookup_strided_code
+        elif mode == 'c':
+            funcname = "__Pyx_BufPtrCContig%dd" % nd
+            funcgen = buf_lookup_c_code
+        elif mode == 'fortran':
+            funcname = "__Pyx_BufPtrFortranContig%dd" % nd
+            funcgen = buf_lookup_fortran_code
+        else:
+            assert False
         for i, s in zip(index_cnames, bufaux.stridevars):
             params.append(i)
             params.append(s.cname)
-        funcname = "__Pyx_BufPtrStrided%dd" % nd
-        funcgen = buf_lookup_strided_code
         
     # Make sure the utility code is available
     code.globalstate.use_generated_code(funcgen, name=funcname, nd=nd)
 
-    ptrcode = "%s(%s.buf, %s)" % (funcname, bufstruct, ", ".join(params))
-    return entry.type.buffer_ptr_type.cast_code(ptrcode)
+    ptr_type = entry.type.buffer_ptr_type
+    ptrcode = "%s(%s, %s.buf, %s)" % (funcname,
+                                      ptr_type.declaration_code(""),
+                                      bufstruct,
+                                      ", ".join(params))
+    return ptrcode
 
 
 def use_empty_bufstruct_code(env, max_ndim):
@@ -399,33 +419,59 @@ def use_empty_bufstruct_code(env, max_ndim):
     env.use_utility_code([code, ""], "empty_bufstruct_code")
 
 
-def buf_lookup_strided_code(proto, defin, name, nd):
-    """
-    Generates a buffer lookup function for the right number
-    of dimensions. The function gives back a void* at the right location.
-    """
-    # _i_ndex, _s_tride
-    args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
-    offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd)])
-    proto.putln("#define %s(buf, %s) ((char*)buf + %s)" % (name, args, offset))
-
 def buf_lookup_full_code(proto, defin, name, nd):
     """
     Generates a buffer lookup function for the right number
     of dimensions. The function gives back a void* at the right location.
     """
     # _i_ndex, _s_tride, sub_o_ffset
-    args = ", ".join(["Py_ssize_t i%d, Py_ssize_t s%d, Py_ssize_t o%d" % (i, i, i) for i in range(nd)])
-    proto.putln("static INLINE void* %s(void* buf, %s);" % (name, args))
+    macroargs = ", ".join(["i%d, s%d, o%d" % (i, i, i) for i in range(nd)])
+    proto.putln("#define %s(type, buf, %s) (type)(%s_imp(buf, %s))" % (name, macroargs, name, macroargs))
+
+    funcargs = ", ".join(["Py_ssize_t i%d, Py_ssize_t s%d, Py_ssize_t o%d" % (i, i, i) for i in range(nd)])
+    proto.putln("static INLINE void* %s_imp(void* buf, %s);" % (name, funcargs))
     defin.putln(dedent("""
-        static INLINE void* %s(void* buf, %s) {
+        static INLINE void* %s_imp(void* buf, %s) {
           char* ptr = (char*)buf;
-        """) % (name, args) + "".join([dedent("""\
+        """) % (name, funcargs) + "".join([dedent("""\
           ptr += s%d * i%d;
           if (o%d >= 0) ptr = *((char**)ptr) + o%d; 
         """) % (i, i, i, i) for i in range(nd)]
         ) + "\nreturn ptr;\n}")
 
+def buf_lookup_strided_code(proto, defin, name, nd):
+    """
+    Generates a buffer lookup function for the right number
+    of dimensions. The function gives back a void* at the right location.
+    """
+    # _i_ndex, _s_tride
+    args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
+    offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd)])
+    proto.putln("#define %s(type, buf, %s) (type)((char*)buf + %s)" % (name, args, offset))
+
+def buf_lookup_c_code(proto, defin, name, nd):
+    """
+    Similar to strided lookup, but can assume that the last dimension
+    doesn't need a multiplication as long as.
+    Still we keep the same signature for now.
+    """
+    if nd == 1:
+        proto.putln("#define %s(type, buf, i0, s0) ((type)buf + i0)" % name)
+    else:
+        args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
+        offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd - 1)])
+        proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, nd - 1))
+
+def buf_lookup_fortran_code(proto, defin, name, nd):
+    """
+    Like C lookup, but the first index is optimized instead.
+    """
+    if nd == 1:
+        proto.putln("#define %s(type, buf, i0, s0) ((type)buf + i0)" % name)
+    else:
+        args = ", ".join(["i%d, s%d" % (i, i) for i in range(nd)])
+        offset = " + ".join(["i%d * s%d" % (i, i) for i in range(1, nd)])
+        proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, 0))
 
 #
 # Utils for creating type string checkers
index 5d9874182d396fbec74c6dae699d8b109ed52271..23989de89577d280a7aa1fc46860300839a37ef5 100644 (file)
@@ -1,3 +1,5 @@
+cimport python_buffer as pybuf
+
 cdef extern from "Python.h":
     ctypedef int Py_intptr_t
     
@@ -19,7 +21,11 @@ cdef extern from "numpy/arrayobject.h":
         NPY_NTYPES,
         NPY_NOTYPE,
         NPY_CHAR,  
-        NPY_USERDEF
+        NPY_USERDEF,
+
+        NPY_C_CONTIGUOUS,
+        NPY_F_CONTIGUOUS
+        
 
     ctypedef class numpy.ndarray [object PyArrayObject]:
         cdef __cythonbufferdefaults__ = {"mode": "strided"}
@@ -29,19 +35,27 @@ cdef extern from "numpy/arrayobject.h":
             int ndim "nd"
             npy_intp *shape "dimensions" 
             npy_intp *strides
+            int flags
 
         # Note: This syntax (function definition in pxd files) is an
         # experimental exception made for __getbuffer__ and __releasebuffer__
         # -- the details of this may change.
         def __getbuffer__(ndarray self, Py_buffer* info, int flags):
             # This implementation of getbuffer is geared towards Cython
-            # requirements, and does not yet fullfill the PEP (specifically,
-            # Cython always requests and we always provide strided access,
-            # so the flags are not even checked).
-            
+            # requirements, and does not yet fullfill the PEP.
+            # In particular strided access is always provided regardless
+            # of flags
             if sizeof(npy_intp) != sizeof(Py_ssize_t):
                 raise RuntimeError("Py_intptr_t and Py_ssize_t differs in size, numpy.pxd does not support this")
 
+            if ((flags & pybuf.PyBUF_C_CONTIGUOUS == pybuf.PyBUF_C_CONTIGUOUS)
+                and not PyArray_CHKFLAGS(self, NPY_C_CONTIGUOUS)):
+                raise ValueError("ndarray is not C contiguous")
+                
+            if ((flags & pybuf.PyBUF_F_CONTIGUOUS == pybuf.PyBUF_F_CONTIGUOUS)
+                and not PyArray_CHKFLAGS(self, NPY_F_CONTIGUOUS)):
+                raise ValueError("ndarray is not Fortran contiguous")
+
             info.buf = PyArray_DATA(self)
             # info.obj = None # this is automatic
             info.ndim = PyArray_NDIM(self)
@@ -82,6 +96,7 @@ cdef extern from "numpy/arrayobject.h":
     cdef npy_intp PyArray_STRIDES(ndarray arr)
     cdef npy_intp PyArray_DIMS(ndarray arr)
     cdef Py_ssize_t PyArray_ITEMSIZE(ndarray arr)
+    cdef int PyArray_CHKFLAGS(ndarray arr, int flags)
 
     ctypedef signed int   npy_byte
     ctypedef signed int   npy_short
index 3a571297b97843bad2b692337179b65a6de5641e..7498a010bf33fc1a2314adfb9ab9187e796f798d 100644 (file)
@@ -553,6 +553,54 @@ def strided(object[int, ndim=1, mode='strided'] buf):
     """
     return buf[2]
 
+@testcase
+def c_contig(object[int, ndim=1, mode='c'] buf):
+    """
+    >>> A = IntMockBuffer(None, range(4))
+    >>> c_contig(A)
+    2
+    >>> [str(x) for x in A.recieved_flags]
+    ['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS']
+    """
+    return buf[2]
+    
+@testcase
+def c_contig_2d(object[int, ndim=2, mode='c'] buf):
+    """
+    Multi-dim has seperate implementation
+    
+    >>> A = IntMockBuffer(None, range(12), shape=(3,4))
+    >>> c_contig_2d(A)
+    7
+    >>> [str(x) for x in A.recieved_flags]
+    ['FORMAT', 'ND', 'STRIDES', 'C_CONTIGUOUS']
+    """
+    return buf[1, 3]
+
+@testcase
+def f_contig(object[int, ndim=1, mode='fortran'] buf):
+    """
+    >>> A = IntMockBuffer(None, range(4))
+    >>> f_contig(A)
+    2
+    >>> [str(x) for x in A.recieved_flags]
+    ['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS']
+    """
+    return buf[2]
+
+@testcase
+def f_contig_2d(object[int, ndim=2, mode='fortran'] buf):
+    """
+    Must set up strides manually to ensure Fortran ordering.
+    
+    >>> A = IntMockBuffer(None, range(12), shape=(4,3), strides=(1, 4))
+    >>> f_contig_2d(A)
+    7
+    >>> [str(x) for x in A.recieved_flags]
+    ['FORMAT', 'ND', 'STRIDES', 'F_CONTIGUOUS']
+    """
+    return buf[3, 1]
+
 #
 # Test compiler options for bounds checking. We create an array with a
 # safe "boundary" (memory
@@ -877,6 +925,8 @@ available_flags = (
     ('INDIRECT', python_buffer.PyBUF_INDIRECT),
     ('ND', python_buffer.PyBUF_ND),
     ('STRIDES', python_buffer.PyBUF_STRIDES),
+    ('C_CONTIGUOUS', python_buffer.PyBUF_C_CONTIGUOUS),
+    ('F_CONTIGUOUS', python_buffer.PyBUF_F_CONTIGUOUS),
     ('WRITABLE', python_buffer.PyBUF_WRITABLE)
 )
 
@@ -913,7 +963,6 @@ cdef class MockBuffer:
             strides.reverse()
         strides = [x * self.itemsize for x in strides]
         suboffsets = [-1] * len(shape)
-
         datashape = [len(data)]
         p = data
         while True:
index a07fb6e190dc95099eb8a30f970f4d39cf824e8d..6b141358c54544156d3dc961073d142ae33fae62 100644 (file)
@@ -78,6 +78,30 @@ try:
     >>> print a
     [[0 0 0 0 0]
      [0 0 0 0 0]]
+
+    Test contiguous access modes:
+    >>> c_arr = np.array(np.arange(12, dtype='i').reshape(3,4), order='C')
+    >>> f_arr = np.array(np.arange(12, dtype='i').reshape(3,4), order='F')
+    >>> test_c_contig(c_arr)
+    0 1 2 3
+    4 5 6 7
+    8 9 10 11
+    >>> test_f_contig(f_arr)
+    0 1 2 3
+    4 5 6 7
+    8 9 10 11
+    >>> test_c_contig(f_arr)
+    Traceback (most recent call last):
+       ...
+    ValueError: ndarray is not C contiguous
+    >>> test_f_contig(c_arr)
+    Traceback (most recent call last):
+       ...
+    ValueError: ndarray is not Fortran contiguous
+    >>> test_c_contig(c_arr[::2,::2])
+    Traceback (most recent call last):
+       ...
+    ValueError: ndarray is not C contiguous
     
     >>> test_dtype('b', inc1_byte)
     >>> test_dtype('B', inc1_ubyte)
@@ -153,6 +177,15 @@ def put_range_long_1d(np.ndarray[long] arr):
         arr[i] = value
         value += 1
 
+def test_c_contig(np.ndarray[int, ndim=2, mode='c'] arr):
+    cdef int i, j
+    for i in range(arr.shape[0]):
+        print " ".join([str(arr[i, j]) for j in range(arr.shape[1])])
+
+def test_f_contig(np.ndarray[int, ndim=2, mode='fortran'] arr):
+    cdef int i, j
+    for i in range(arr.shape[0]):
+        print " ".join([str(arr[i, j]) for j in range(arr.shape[1])])
 
 # Exhaustive dtype tests -- increments element [1] by 1 for all dtypes
 def inc1_byte(np.ndarray[char] arr):                    arr[1] += 1