Author: Stefan H. Muller <[email protected]>
Branch: pypy-pyarray
Changeset: r65896:afe17c743cf7
Date: 2013-07-28 15:12 +0200
http://bitbucket.org/pypy/pypy/changeset/afe17c743cf7/

Log:    - Add cpyext implementation of Numpy PyArray_* C-API
           * pypy/module/cpyext/include/numpy/arrayobject.h
           * pypy/module/cpyext/ndarrayobject.py
           * pypy/module/cpyext/test/test_ndarrayobject.py
        - pypy/module/cpyext/api.py: copy_header_files() now copies the numpy
        subdirectory as well.
        - pypy/module/micronumpy/interp_dtype.py: DtypeCache.dtypes_by_num:
           * Keep in dictionary form, since otherwise not all dtypes can be
        reached.
        - lib_pypy/numpy.py, lib_pypy/numpypy/__init__.py:
           * "import numpy" now displays a warning but falls back to "import
        numpypy as numpy" *without* raising an ImportError.
        - pypy/module/cpyext/include/boolobject.h and complexobject.h:
           * Add #define's for PyIntObject and PyComplexObject.

diff --git a/lib_pypy/numpy.py b/lib_pypy/numpy.py
--- a/lib_pypy/numpy.py
+++ b/lib_pypy/numpy.py
@@ -1,5 +1,14 @@
-raise ImportError(
+import warnings
+
+warnings.warn(
     "The 'numpy' module of PyPy is in-development and not complete. "
-    "To try it out anyway, you can either import from 'numpypy', "
-    "or just write 'import numpypy' first in your program and then "
-    "import from 'numpy' as usual.")
+    "To avoid this warning, write 'import numpypy as numpy'. ")
+
+from numpypy import *
+
+import os
+
+def get_include():
+    head, tail = os.path.split(os.path.dirname(os.path.abspath(__file__)))
+    return os.path.join(head, 'include')
+
diff --git a/lib_pypy/numpypy/__init__.py b/lib_pypy/numpypy/__init__.py
--- a/lib_pypy/numpypy/__init__.py
+++ b/lib_pypy/numpypy/__init__.py
@@ -10,5 +10,5 @@
 __all__ += core.__all__
 __all__ += lib.__all__
 
-import sys
-sys.modules.setdefault('numpy', sys.modules['numpypy'])
+#import sys
+#sys.modules.setdefault('numpy', sys.modules['numpypy'])
diff --git a/pypy/module/cpyext/__init__.py b/pypy/module/cpyext/__init__.py
--- a/pypy/module/cpyext/__init__.py
+++ b/pypy/module/cpyext/__init__.py
@@ -36,6 +36,7 @@
 import pypy.module.cpyext.object
 import pypy.module.cpyext.stringobject
 import pypy.module.cpyext.tupleobject
+import pypy.module.cpyext.ndarrayobject
 import pypy.module.cpyext.setobject
 import pypy.module.cpyext.dictobject
 import pypy.module.cpyext.intobject
diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -130,11 +130,7 @@
 udir.join('pypy_macros.h').write("/* Will be filled later */\n")
 globals().update(rffi_platform.configure(CConfig_constants))
 
-def copy_header_files(dstdir):
-    assert dstdir.check(dir=True)
-    headers = include_dir.listdir('*.h') + include_dir.listdir('*.inl')
-    for name in ("pypy_decl.h", "pypy_macros.h"):
-        headers.append(udir.join(name))
+def _copy_header_files(headers, dstdir):
     for header in headers:
         target = dstdir.join(header.basename)
         try:
@@ -145,6 +141,25 @@
         target.chmod(0444) # make the file read-only, to make sure that nobody
                            # edits it by mistake
 
+def copy_header_files(dstdir):
+    # XXX: 20 lines of code to recursively copy a directory, really??
+    assert dstdir.check(dir=True)
+    headers = include_dir.listdir('*.h') + include_dir.listdir('*.inl')
+    for name in ("pypy_decl.h", "pypy_macros.h"):
+        headers.append(udir.join(name))
+    _copy_header_files(headers, dstdir)
+
+    try:
+        dstdir.mkdir('numpy')
+    except py.error.EEXIST:
+        pass
+    numpy_dstdir = dstdir / 'numpy'
+
+    numpy_include_dir = include_dir / 'numpy'
+    numpy_headers = numpy_include_dir.listdir('*.h') + 
numpy_include_dir.listdir('*.inl')
+    _copy_header_files(numpy_headers, numpy_dstdir)
+
+    
 class NotSpecified(object):
     pass
 _NOT_SPECIFIED = NotSpecified()
diff --git a/pypy/module/cpyext/include/boolobject.h 
b/pypy/module/cpyext/include/boolobject.h
--- a/pypy/module/cpyext/include/boolobject.h
+++ b/pypy/module/cpyext/include/boolobject.h
@@ -7,6 +7,8 @@
 extern "C" {
 #endif
 
+#define PyBoolObject PyIntObject
+
 #define Py_False ((PyObject *) &_Py_ZeroStruct)
 #define Py_True ((PyObject *) &_Py_TrueStruct)
 
diff --git a/pypy/module/cpyext/include/complexobject.h 
b/pypy/module/cpyext/include/complexobject.h
--- a/pypy/module/cpyext/include/complexobject.h
+++ b/pypy/module/cpyext/include/complexobject.h
@@ -6,6 +6,9 @@
 extern "C" {
 #endif
 
+/* fake PyComplexObject so that code that doesn't do direct field access works 
*/
+#define PyComplexObject PyObject
+
 typedef struct Py_complex_t {
     double real;
     double imag;
diff --git a/pypy/module/cpyext/include/numpy/arrayobject.h 
b/pypy/module/cpyext/include/numpy/arrayobject.h
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/include/numpy/arrayobject.h
@@ -0,0 +1,52 @@
+
+/* NDArray object interface - S. H. Muller, 2013/07/26 */
+
+#ifndef Py_NDARRAYOBJECT_H
+#define Py_NDARRAYOBJECT_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* fake PyArrayObject so that code that doesn't do direct field access works */
+#define PyArrayObject PyObject
+
+#ifndef npy_intp
+#define npy_intp long
+#endif
+#ifndef import_array
+#define import_array()
+#endif
+
+/* copied from numpy/ndarraytypes.h 
+ * keep numbers in sync with micronumpy.interp_dtype.DTypeCache
+ */
+enum NPY_TYPES {    NPY_BOOL=0,
+                    NPY_BYTE, NPY_UBYTE,
+                    NPY_SHORT, NPY_USHORT,
+                    NPY_INT, NPY_UINT,
+                    NPY_LONG, NPY_ULONG,
+                    NPY_LONGLONG, NPY_ULONGLONG,
+                    NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE,
+                    NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE,
+                    NPY_OBJECT=17,
+                    NPY_STRING, NPY_UNICODE,
+                    NPY_VOID,
+                    /*
+                     * New 1.6 types appended, may be integrated
+                     * into the above in 2.0.
+                     */
+                    NPY_DATETIME, NPY_TIMEDELTA, NPY_HALF,
+
+                    NPY_NTYPES,
+                    NPY_NOTYPE,
+                    NPY_CHAR,      /* special flag */
+                    NPY_USERDEF=256,  /* leave room for characters */
+
+                    /* The number of types not including the new 1.6 types */
+                    NPY_NTYPES_ABI_COMPATIBLE=21
+};
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py_NDARRAYOBJECT_H */
diff --git a/pypy/module/cpyext/ndarrayobject.py 
b/pypy/module/cpyext/ndarrayobject.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/ndarrayobject.py
@@ -0,0 +1,106 @@
+"""
+Numpy C-API for PyPy - S. H. Muller, 2013/07/26
+"""
+
+from rpython.rtyper.lltypesystem import rffi, lltype
+from pypy.module.cpyext.api import cpython_api, Py_ssize_t, CANNOT_FAIL
+from pypy.module.cpyext.pyobject import PyObject
+from pypy.module.micronumpy.interp_numarray import W_NDimArray, 
convert_to_array
+from pypy.module.micronumpy.interp_dtype import get_dtype_cache
+from pypy.module.micronumpy.arrayimpl.scalar import Scalar
+from rpython.rlib.rawstorage import RAW_STORAGE_PTR
+
+# the asserts are needed, otherwise the translation fails
+
+@cpython_api([PyObject], Py_ssize_t, error=CANNOT_FAIL)
+def PyArray_NDIM(space, w_array):
+    assert isinstance(w_array, W_NDimArray)
+    return len(w_array.get_shape())
+
+@cpython_api([PyObject, Py_ssize_t], Py_ssize_t, error=CANNOT_FAIL)
+def PyArray_DIM(space, w_array, n):
+    assert isinstance(w_array, W_NDimArray)
+    return w_array.get_shape()[n]
+
+@cpython_api([PyObject], Py_ssize_t, error=CANNOT_FAIL)
+def PyArray_SIZE(space, w_array):
+    assert isinstance(w_array, W_NDimArray)
+    return w_array.get_size()
+
+@cpython_api([PyObject], Py_ssize_t, error=CANNOT_FAIL)
+def PyArray_ITEMSIZE(space, w_array):
+    assert isinstance(w_array, W_NDimArray)
+    return w_array.get_dtype().get_size()
+
+@cpython_api([PyObject], Py_ssize_t, error=CANNOT_FAIL)
+def PyArray_NBYTES(space, w_array):
+    assert isinstance(w_array, W_NDimArray)
+    return w_array.get_size() * w_array.get_dtype().get_size()
+
+@cpython_api([PyObject], Py_ssize_t, error=CANNOT_FAIL)
+def PyArray_TYPE(space, w_array):
+    assert isinstance(w_array, W_NDimArray)
+    return w_array.get_dtype().num
+
+
+@cpython_api([PyObject], rffi.VOIDP, error=CANNOT_FAIL)
+def PyArray_DATA(space, w_array):
+    # fails on scalars - see PyArray_FromAny()
+    assert isinstance(w_array, W_NDimArray)
+    return rffi.cast(rffi.VOIDP, w_array.implementation.storage)
+
+
+@cpython_api([PyObject, rffi.VOIDP, Py_ssize_t, Py_ssize_t, Py_ssize_t, 
rffi.VOIDP], 
+             PyObject)
+def PyArray_FromAny(space, w_obj, dtype, min_depth, max_depth, requirements, 
context):
+    # ignore all additional arguments for now
+    w_array = convert_to_array(space, w_obj)
+    if w_array.is_scalar():
+        # since PyArray_DATA() fails on scalars, create a 1D array and set 
empty 
+        # shape. So the following combination works for *reading* scalars:
+        #     PyObject *arr = PyArray_FromAny(obj);
+        #     int nd = PyArray_NDIM(arr);
+        #     void *data = PyArray_DATA(arr);
+        impl = w_array.implementation
+        w_array = W_NDimArray.from_shape(space, [1], impl.dtype)
+        w_array.implementation.setitem(0, impl.value)
+        w_array.implementation.shape = []
+    return w_array
+
+
+@cpython_api([Py_ssize_t, rffi.LONGP, Py_ssize_t], PyObject)
+def PyArray_SimpleNew(space, nd, dims, typenum):
+    dtype = get_dtype_cache(space).dtypes_by_num[typenum]
+    shape = []
+    for i in range(nd):
+        # back-and-forth wrapping needed to translate
+        shape.append(space.int_w(space.wrap(dims[i])))
+
+    return W_NDimArray.from_shape(space, shape, dtype)
+
+
+def simple_new_from_data(space, nd, dims, typenum, data, owning):
+    dtype = get_dtype_cache(space).dtypes_by_num[typenum]
+    storage = rffi.cast(RAW_STORAGE_PTR, data)
+    if nd == 0:
+        w_val = dtype.itemtype.box_raw_data(storage)
+        return W_NDimArray(Scalar(dtype, w_val))
+    else:
+        shape = []
+        for i in range(nd):
+            # back-and-forth wrapping needed to translate
+            shape.append(space.int_w(space.wrap(dims[i])))
+        
+        return W_NDimArray.from_shape_and_storage(space, shape, storage, 
dtype, owning=owning)
+
+@cpython_api([Py_ssize_t, rffi.LONGP, Py_ssize_t, rffi.VOIDP], PyObject)
+def PyArray_SimpleNewFromData(space, nd, dims, typenum, data):
+    return simple_new_from_data(space, nd, dims, typenum, data, owning=False)
+
+@cpython_api([Py_ssize_t, rffi.LONGP, Py_ssize_t, rffi.VOIDP], PyObject)
+def PyArray_SimpleNewFromDataOwning(space, nd, dims, typenum, data):
+    # Variant to take over ownership of the memory, equivalent to:
+    #     PyObject *arr = PyArray_SimpleNewFromData(nd, dims, typenum, data);
+    #     ((PyArrayObject*)arr)->flags |= NPY_OWNDATA;
+    return simple_new_from_data(space, nd, dims, typenum, data, owning=True)
+
diff --git a/pypy/module/cpyext/test/test_ndarrayobject.py 
b/pypy/module/cpyext/test/test_ndarrayobject.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/test/test_ndarrayobject.py
@@ -0,0 +1,179 @@
+import py
+
+from pypy.module.cpyext.pyobject import PyObject
+from pypy.module.cpyext.test.test_api import BaseApiTest
+from rpython.rtyper.lltypesystem import rffi, lltype
+
+from pypy.module.micronumpy.interp_numarray import W_NDimArray
+from pypy.module.micronumpy.interp_dtype import get_dtype_cache
+
+def scalar(space):
+    dtype = get_dtype_cache(space).w_float64dtype
+    return W_NDimArray.new_scalar(space, dtype, space.wrap(10.))
+
+def array(space, shape):
+    dtype = get_dtype_cache(space).w_float64dtype
+    return W_NDimArray.from_shape(space, shape, dtype, order='C')
+
+def iarray(space, shape):
+    dtype = get_dtype_cache(space).w_int64dtype
+    return W_NDimArray.from_shape(space, shape, dtype, order='C')
+
+
+NULL = lltype.nullptr(rffi.VOIDP.TO)
+
+class TestNDArrayObject(BaseApiTest):
+
+    def test_NDIM(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_NDIM(a) == 3
+
+    def test_DIM(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_DIM(a, 1) == 5
+
+    def test_SIZE(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_SIZE(a) == 150
+
+    def test_ITEMSIZE(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_ITEMSIZE(a) == 8
+    
+    def test_NBYTES(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_NBYTES(a) == 1200
+
+    def test_TYPE(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_TYPE(a) == 12
+
+    def test_DATA(self, space, api):
+        a = array(space, [10, 5, 3])
+        addr = api.PyArray_DATA(a)
+        addr2 = rffi.cast(rffi.VOIDP, a.implementation.storage)
+        assert addr == addr2
+
+    def test_FromAny_scalar(self, space, api):
+        a0 = scalar(space)
+        assert a0.implementation.get_scalar_value().value == 10.
+
+        a = api.PyArray_FromAny(a0, NULL, 0, 0, 0, NULL)
+        assert api.PyArray_NDIM(a) == 0
+        
+        ptr = rffi.cast(rffi.DOUBLEP, api.PyArray_DATA(a))
+        assert ptr[0] == 10.
+
+    def test_FromAny(self, space, api):
+        a = array(space, [10, 5, 3])
+        assert api.PyArray_FromAny(a, NULL, 0, 0, 0, NULL) is a
+
+    def test_list_from_fixedptr(self, space, api):
+        A = lltype.GcArray(lltype.Float)
+        ptr = lltype.malloc(A, 3)
+        assert isinstance(ptr, lltype._ptr)
+        ptr[0] = 10.
+        ptr[1] = 5.
+        ptr[2] = 3.
+        l = list(ptr)
+        assert l == [10., 5., 3.]
+
+    def test_list_from_openptr(self, space, api):
+        nd = 3
+        a = array(space, [nd])
+        ptr = rffi.cast(rffi.DOUBLEP, api.PyArray_DATA(a))
+        ptr[0] = 10.
+        ptr[1] = 5.
+        ptr[2] = 3.
+        l = []
+        for i in range(nd):
+            l.append(ptr[i])
+        assert l == [10., 5., 3.]
+
+    def test_SimpleNew_scalar(self, space, api):
+        ptr_s = lltype.nullptr(rffi.LONGP.TO)
+        a = api.PyArray_SimpleNew(0, ptr_s, 12)
+        
+        dtype = get_dtype_cache(space).w_float64dtype
+        
+        a.set_scalar_value(dtype.itemtype.box(10.))
+        assert a.get_scalar_value().value == 10.
+
+    def test_SimpleNewFromData_scalar(self, space, api):
+        a = array(space, [1])
+        num = api.PyArray_TYPE(a)
+        ptr_a = api.PyArray_DATA(a)
+
+        x = rffi.cast(rffi.DOUBLEP, ptr_a)
+        x[0] = float(10.)
+
+        ptr_s = lltype.nullptr(rffi.LONGP.TO)
+        
+        res = api.PyArray_SimpleNewFromData(0, ptr_s, num, ptr_a)
+        assert res.is_scalar()
+        assert res.get_scalar_value().value == 10.
+
+    def test_SimpleNew(self, space, api):
+        shape = [10, 5, 3]
+        nd = len(shape)
+
+        s = iarray(space, [nd])
+        ptr_s = rffi.cast(rffi.LONGP, api.PyArray_DATA(s))
+        ptr_s[0] = 10
+        ptr_s[1] = 5
+        ptr_s[2] = 3
+        
+        a = api.PyArray_SimpleNew(nd, ptr_s, 12)
+        
+        #assert list(api.PyArray_DIMS(a))[:3] == shape
+
+        ptr_a = api.PyArray_DATA(a)
+
+        x = rffi.cast(rffi.DOUBLEP, ptr_a)
+        for i in range(150):
+            x[i] = float(i)
+
+        for i in range(150):
+            assert x[i] == float(i)
+
+    def test_SimpleNewFromData(self, space, api):
+        shape = [10, 5, 3]
+        nd = len(shape)
+
+        s = iarray(space, [nd])
+        ptr_s = rffi.cast(rffi.LONGP, api.PyArray_DATA(s))
+        ptr_s[0] = 10
+        ptr_s[1] = 5
+        ptr_s[2] = 3
+
+        a = array(space, shape)
+        num = api.PyArray_TYPE(a)
+        ptr_a = api.PyArray_DATA(a)
+
+        x = rffi.cast(rffi.DOUBLEP, ptr_a)
+        for i in range(150):
+            x[i] = float(i)
+
+        res = api.PyArray_SimpleNewFromData(nd, ptr_s, num, ptr_a)
+        assert api.PyArray_TYPE(res) == num
+        assert api.PyArray_DATA(res) == ptr_a
+        for i in range(nd):
+            assert api.PyArray_DIM(res, i) == shape[i]
+        ptr_r = rffi.cast(rffi.DOUBLEP, api.PyArray_DATA(res))
+        for i in range(150):
+            assert ptr_r[i] == float(i)
+
+    def test_SimpleNewFromData_complex(self, space, api):
+        a = array(space, [2])
+        ptr_a = api.PyArray_DATA(a)
+        
+        x = rffi.cast(rffi.DOUBLEP, ptr_a)
+        x[0] = 3.
+        x[1] = 4.
+                
+        ptr_s = lltype.nullptr(rffi.LONGP.TO)
+        
+        res = api.PyArray_SimpleNewFromData(0, ptr_s, 15, ptr_a)
+        assert res.get_scalar_value().real == 3.
+        assert res.get_scalar_value().imag == 4.
+
diff --git a/pypy/module/micronumpy/interp_dtype.py 
b/pypy/module/micronumpy/interp_dtype.py
--- a/pypy/module/micronumpy/interp_dtype.py
+++ b/pypy/module/micronumpy/interp_dtype.py
@@ -804,10 +804,12 @@
                 self.dtypes_by_name[alias] = dtype
             self.dtypes_by_name[dtype.char] = dtype
 
-        self.dtypes_by_num = [dtype for dtype in
-                sorted(self.dtypes_by_num.values(), key=lambda dtype: 
dtype.num)
-                if dtype.num <= self.w_float64dtype.num]
-        assert len(self.dtypes_by_num) == self.w_float64dtype.num + 1
+        # shmuller 2013/07/22: Cannot find complex data types after conversion 
to
+        #                      list! Solution: Keep in dictionary form.
+        #self.dtypes_by_num = [dtype for dtype in
+        #        sorted(self.dtypes_by_num.values(), key=lambda dtype: 
dtype.num)
+        #        if dtype.num <= self.w_float64dtype.num]
+        #assert len(self.dtypes_by_num) == self.w_float64dtype.num + 1
 
         typeinfo_full = {
             'LONGLONG': self.w_int64dtype,
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to