[pypy-commit] pypy ufuncapi: handle the difference between the GenericUfuncApi and the GeneralizedUfuncApi

mattip noreply at buildbot.pypy.org
Sat Nov 29 21:32:01 CET 2014


Author: mattip <matti.picus at gmail.com>
Branch: ufuncapi
Changeset: r74760:8651d4080984
Date: 2014-11-29 22:28 +0200
http://bitbucket.org/pypy/pypy/changeset/8651d4080984/

Log:	handle the difference between the GenericUfuncApi and the
	GeneralizedUfuncApi

diff --git a/pypy/module/cpyext/ndarrayobject.py b/pypy/module/cpyext/ndarrayobject.py
--- a/pypy/module/cpyext/ndarrayobject.py
+++ b/pypy/module/cpyext/ndarrayobject.py
@@ -265,6 +265,13 @@
               rffi.CCHARP], PyObject)
 def PyUFunc_FromFuncAndDataAndSignature(space, funcs, data, types, ntypes,
                     nin, nout, identity, name, doc, check_return, signature):
+    w_signature = rffi.charp2str(signature)
+    return do_ufunc(space, funcs, data, types, ntypes, nin, nout, identity, name, doc,
+             check_return, w_signature)
+            
+
+def do_ufunc(space, funcs, data, types, ntypes, nin, nout, identity, name, doc,
+             check_return, w_signature):
     funcs_w = [None] * ntypes
     dtypes_w = [None] * ntypes * (nin + nout)
     for i in range(ntypes):
@@ -273,10 +280,17 @@
         dtypes_w[i] = get_dtype_cache(space).dtypes_by_num[ord(types[i])]
     w_funcs = space.newlist(funcs_w)
     w_dtypes = space.newlist(dtypes_w)
-    w_signature = rffi.charp2str(signature)
     w_doc = rffi.charp2str(doc)
     w_name = rffi.charp2str(name)
     w_identity = space.wrap(identity)
     ufunc_generic = ufuncs.frompyfunc(space, w_funcs, nin, nout, w_dtypes,
                  w_signature, w_identity, w_name, w_doc, stack_inputs=True)
     return ufunc_generic
+
+ at cpython_api([rffi.CArrayPtr(rffi.CArrayPtr(gufunctype)), rffi.VOIDP, rffi.CCHARP, Py_ssize_t, Py_ssize_t,
+              Py_ssize_t, Py_ssize_t, rffi.CCHARP, rffi.CCHARP, Py_ssize_t], PyObject)
+def PyUFunc_FromFuncAndData(space, funcs, data, types, ntypes,
+                    nin, nout, identity, name, doc, check_return):
+    w_signature = ""
+    return do_ufunc(space, funcs, data, types, ntypes, nin, nout, identity,
+                    name, doc, check_return, w_signature)
diff --git a/pypy/module/cpyext/test/test_ndarrayobject.py b/pypy/module/cpyext/test/test_ndarrayobject.py
--- a/pypy/module/cpyext/test/test_ndarrayobject.py
+++ b/pypy/module/cpyext/test/test_ndarrayobject.py
@@ -322,9 +322,9 @@
                 char types[] = { NPY_DOUBLE,NPY_DOUBLE, NPY_INT, NPY_INT };
                 void *array_data[] = {NULL, NULL};
                 PyObject * retval;
-                retval = PyUFunc_FromFuncAndDataAndSignature(funcs,
+                retval = PyUFunc_FromFuncAndData(funcs,
                                     array_data, types, 2, 1, 1, PyUFunc_None,
-                                    "times2", "times2_docstring", 0, "()->()");
+                                    "times2", "times2_docstring", 0);
                 return retval;
                 """
                 ),
@@ -336,13 +336,23 @@
                 PyObject * retval;
                 retval = PyUFunc_FromFuncAndDataAndSignature(funcs,
                                     array_data, types, 2, 1, 1, PyUFunc_None,
-                                    "times2", "times2_docstring", 0, "(m)->(m)");
+                                    "times2", "times2_docstring", 0, "()->()");
                 return retval;
+                """),
+                ("create_float_ufunc_3x3", "METH_NOARGS",
                 """
-                ),
+                PyUFuncGenericFunction funcs[] = {&float_func_with_sig_3x3};
+                char types[] = { NPY_FLOAT,NPY_FLOAT};
+                void *array_data[] = {NULL, NULL};
+                return PyUFunc_FromFuncAndDataAndSignature(funcs,
+                                    array_data, types, 1, 1, 1, PyUFunc_None,
+                                    "float_3x3", 
+                                    "a ufunc that tests a more complicated signature", 
+                                    0, "(m,m)->(m,m)");
+                """),
                 ], prologue='''
                 #include "numpy/ndarraytypes.h"
-                /*#include <numpy/ufuncobject.h>*/
+                /*#include <numpy/ufuncobject.h> generated by numpy setup.py*/
                 typedef void (*PyUFuncGenericFunction)
                             (char **args,
                              npy_intp *dimensions,
@@ -390,11 +400,34 @@
                         in += in_step;
                         out += out_step;
                     };
-                }; ''')
+                };
+                void float_func_with_sig_3x3(char ** args, npy_intp * dimensions,
+                              npy_intp* steps, void* data)
+                {
+                    int target_dims[] = {1, 3};
+                    int target_steps[] = {0, 0, 12, 4, 12, 4};
+                    int res = 0;
+                    int i;
+                    for (i=0; i<sizeof(target_dims)/sizeof(int); i++)
+                        if (dimensions[i] != target_dims[i])
+                            res += 1;
+                    for (i=0; i<sizeof(target_steps)/sizeof(int); i++)
+                        if (steps[i] != target_steps[i])
+                            res += +10;
+                    *((float *)args[1]) = res;
+                };
+                            
+                ''')
+        sq = arange(18, dtype="float32").reshape(2,3,3)
+        float_ufunc = mod.create_float_ufunc_3x3()
+        out = float_ufunc(sq)
+        assert out[0, 0, 0] == 0
+
         times2 = mod.create_ufunc_basic()
         arr = arange(12, dtype='i').reshape(3, 4)
-        out = times2(arr, sig='(d)->(d)', extobj=[0, 0, None])
+        out = times2(arr, extobj=[0, 0, None])
         assert (out == arr * 2).all()
+
         times2prime = mod.create_ufunc_signature()
         out = times2prime(arr, sig='(d)->(d)', extobj=[0, 0, None])
         assert (out == arr * 2).all()
diff --git a/pypy/module/micronumpy/nditer.py b/pypy/module/micronumpy/nditer.py
--- a/pypy/module/micronumpy/nditer.py
+++ b/pypy/module/micronumpy/nditer.py
@@ -256,8 +256,8 @@
     _backstride = [(_shape[fastest] - 1) * _stride[0]] + old_iter.slice_backstride
     if flat:
         _shape = [support.product(_shape)]
-        assert len(_stride) == 2
-        _stride = [min(_stride[0], _stride[1])]
+        if len(_stride) > 1:
+            _stride = [min(_stride[0], _stride[1])]
         _backstride = [(shape[0] - 1) * _stride[0]]
     return SliceIter(old_iter.array, old_iter.size / shape[fastest],
                 new_shape, new_strides, new_backstrides,
diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py
--- a/pypy/module/micronumpy/test/test_ufuncs.py
+++ b/pypy/module/micronumpy/test/test_ufuncs.py
@@ -150,8 +150,9 @@
         ufunc = frompyfunc([int_times2, double_times2], 1, 1,
                             signature='()->()',
                             dtypes=[dtype(int), dtype(int),
-                            dtype(float), dtype(float)
-                            ]
+                                    dtype(float), dtype(float)
+                                    ],
+                            stack_inputs=True,
                     )
         ai = arange(10, dtype=int)
         ai2 = ufunc(ai)
@@ -167,13 +168,23 @@
             out_array[:] = in_array * 2
         from numpy import frompyfunc, dtype, arange
         ufunc = frompyfunc([times_2], 1, 1,
+                            signature='(m,n)->(n,m)',
+                            dtypes=[dtype(int), dtype(int)],
+                            stack_inputs=True,
+                          )
+        ai = arange(18, dtype=int).reshape(2,3,3)
+        ai3 = ufunc(ai[0,:,:])
+        ai2 = ufunc(ai)
+        assert (ai2 == ai * 2).all()
+        ufunc = frompyfunc([times_2], 1, 1,
                             signature='(m,m)->(m,m)',
                             dtypes=[dtype(int), dtype(int)],
                             stack_inputs=True,
                           )
         ai = arange(18, dtype=int).reshape(2,3,3)
         exc = raises(ValueError, ufunc, ai[:,:,0])
-        assert "mismatch in its core dimension 1" in exc.value.message
+        assert "Operand 0 has a mismatch in its core dimension 1" in exc.value.message
+        ai3 = ufunc(ai[0,:,:])
         ai2 = ufunc(ai)
         assert (ai2 == ai * 2).all()
 
diff --git a/pypy/module/micronumpy/ufuncs.py b/pypy/module/micronumpy/ufuncs.py
--- a/pypy/module/micronumpy/ufuncs.py
+++ b/pypy/module/micronumpy/ufuncs.py
@@ -581,8 +581,13 @@
         func = self.funcs[index]
         if not self.core_enabled:
             # func is going to do all the work, it must accept W_NDimArray args
-            arglist = space.newlist(list(inargs + outargs))
-            space.call_args(func, Arguments.frompacked(space, arglist))
+            if self.stack_inputs:
+                arglist = space.newlist(list(inargs + outargs))
+                space.call_args(func, Arguments.frompacked(space, arglist))
+            else:
+                arglist = space.newlist(inargs)
+                outargs = space.call_args(func, Arguments.frompacked(space, arglist))
+                return outargs
             if len(outargs) < 2:
                 return outargs[0]
             return space.newtuple(outargs)
@@ -603,12 +608,9 @@
         for i in range(self.nin, self.nargs):
             iter_ndim += self.core_num_dims[i];
         # Validate the core dimensions of all the operands,
-        # and collect all of the labeled core dimension sizes
-        # into the array 'inner_dimensions[1:]'. Initialize them to
-        # 1, for example in the case where the operand broadcasts
-        # to a core dimension, it won't be visited.
         inner_dimensions = [1] * (self.core_num_dim_ix + 2)
         idim = 0
+        core_start_dim = 0
         for i in range(self.nin):
             curarg = inargs[i]
             assert isinstance(curarg, W_NDimArray)
@@ -706,8 +708,33 @@
         w_itershape = space.newlist([space.wrap(i) for i in iter_shape]) 
         w_op_axes = space.w_None
 
-
         if self.stack_inputs:
+            #print '\nsignature', sig
+            #print [(d, getattr(self,d)) for d in dir(self) if 'core' in d or 'broad' in d]
+            #print [(d, locals()[d]) for d in locals() if 'core' in d or 'broad' in d]
+            #print 'shapes',[d.get_shape() for d in inargs + outargs]
+            #print 'steps',[d.implementation.strides for d in inargs + outargs]
+            if isinstance(func, W_GenericUFuncCaller):
+                # Use GeneralizeUfunc interface with signature
+                # Unlike numpy, we will not broadcast dims before
+                # the core_ndims rather we use nditer iteration
+                # so dims[0] == 1
+                dims = [1] + inner_dimensions[1:]
+                steps = []
+                allargs = inargs + outargs
+                assert core_start_dim >= 0
+                for i in range(len(allargs)):
+                    steps.append(0)
+                for i in range(len(allargs)):
+                    _arg = allargs[i]
+                    assert isinstance(_arg, W_NDimArray)
+                    steps += _arg.implementation.strides[core_start_dim:]
+                #print 'set_dims_and_steps with dims, steps',dims,steps
+                func.set_dims_and_steps(space, dims, steps)
+            else:
+                # it is a function, ready to be called by the iterator,
+                # from frompyfunc
+                pass
             # mimic NpyIter_AdvancedNew with a nditer
             nd_it = W_NDIter(space, space.newlist(inargs + outargs), w_flags,
                           w_op_flags, w_op_dtypes, w_casting, w_op_axes,
@@ -808,14 +835,6 @@
             assert isinstance(outargs[i], W_NDimArray)
         return outargs
 
-    def prep_call(self, space, index, inargs, outargs):
-        # Use the index and signature to determine
-        # dims and steps for function call
-        return self.funcs[index], [inargs[0].get_shape()[0]], \
-                 [inargs[0].implementation.get_strides()[0],
-                  outargs[0].implementation.get_strides()[0]]
-
-
 W_Ufunc.typedef = TypeDef("numpy.ufunc",
     __call__ = interp2app(W_Ufunc.descr_call),
     __repr__ = interp2app(W_Ufunc.descr_repr),
@@ -1176,7 +1195,7 @@
     stack_inputs*: boolean, whether the function is of the form
             out = func(*in)  False
             or
-            func(*in_out)    True (forces use of a nditer with 'external_loop')
+            func(*[in + out])    True 
 
     only one of out_dtype or signature may be specified
 
@@ -1243,8 +1262,8 @@
             'identity must be None or an int')
 
     if len(signature) == 0:
-        # cpython compatability, func is of the form (i),(i)->(i)
-        signature = ','.join(['(i)'] * nin) + '->' + ','.join(['(i)'] * nout)
+        # cpython compatability, func is of the form (),()->()
+        signature = ','.join(['()'] * nin) + '->' + ','.join(['()'] * nout)
     else:
         #stack_inputs = True
         pass
@@ -1257,18 +1276,21 @@
         w_ret.w_doc = space.wrap(doc)
     return w_ret
 
-# Instantiated in cpyext/ndarrayobject
+# Instantiated in cpyext/ndarrayobject. It is here since ufunc calls
+# set_dims_and_steps, otherwise ufunc, ndarrayobject would have circular
+# imports
 npy_intpp = rffi.LONGP
 LONG_SIZE = LONG_BIT / 8
 CCHARP_SIZE = _get_bitsize('P') / 8
 
 class W_GenericUFuncCaller(W_Root):
-    _attrs_ = ['func', 'data', 'dims', 'steps']
+    _attrs_ = ['func', 'data', 'dims', 'steps', 'dims_steps_set']
     def __init__(self, func, data):
         self.func = func
         self.data = data
         self.dims = alloc_raw_storage(0, track_allocation=False)
         self.steps = alloc_raw_storage(0, track_allocation=False)
+        self.dims_steps_set = False
 
     def __del__(self):
         free_raw_storage(self.dims, track_allocation=False)
@@ -1276,10 +1298,14 @@
 
     def descr_call(self, space, __args__):
         args_w, kwds_w = __args__.unpack()
-        # Can be called two ways, as an inner-loop function with boxes, 
-        # or as an outer-looop functio with ndarrays
+        # Can be called two ways, as a GenericUfunc or a GeneralizedUfunc.
+        # The difference is in the meaning of dims and steps,
+        # a GenericUfunc is a scalar function that flatiters over the array(s).
+        # a GeneralizedUfunc will iterate over dims[0], but will use dims[1...]
+        # and steps[1, ...] to call a function on ndarray(s).
+        # set up via a call to set_dims_and_steps()
         dataps = alloc_raw_storage(CCHARP_SIZE * len(args_w), track_allocation=False)
-        if isinstance(args_w[0], W_NDimArray):
+        if self.dims_steps_set is False:
             self.dims = alloc_raw_storage(LONG_SIZE * len(args_w), track_allocation=False)
             self.steps = alloc_raw_storage(LONG_SIZE * len(args_w), track_allocation=False)
             for i in range(len(args_w)):
@@ -1294,13 +1320,10 @@
                 raw_storage_setitem(self.dims, LONG_SIZE * i, rffi.cast(rffi.LONG, arg_i.get_size()))
                 raw_storage_setitem(self.steps, LONG_SIZE * i, rffi.cast(rffi.LONG, arg_i.get_dtype().elsize))
         else:
-            if self.dims is None or self.steps is None:
-                raise OperationError(space.w_RuntimeError,
-                     space.wrap("call set_dims_and_steps first"))
             for i in range(len(args_w)):
                 arg_i = args_w[i]
-                # raw_storage_setitem(dataps, CCHARP_SIZE * i,
-                #       rffi.cast(rffi.CCHARP, arg_i.storage))
+                raw_storage_setitem(dataps, CCHARP_SIZE * i,
+                        rffi.cast(rffi.CCHARP, arg_i.implementation.get_storage_as_int(space)))
         try:
             arg1 = rffi.cast(rffi.CArrayPtr(rffi.CCHARP), dataps)
             arg2 = rffi.cast(npy_intpp, self.dims)
@@ -1309,29 +1332,24 @@
         finally:
             free_raw_storage(dataps, track_allocation=False)
 
+    def set_dims_and_steps(self, space, dims, steps):
+        if not isinstance(dims, list) or not isinstance(steps, list):
+            raise oefmt(space.w_RuntimeError,
+                 "set_dims_and_steps called inappropriately")
+        if self.dims_steps_set:
+            raise oefmt(space.w_RuntimeError,
+                 "set_dims_and_steps called inappropriately")
+        self.dims = alloc_raw_storage(LONG_SIZE * len(dims), track_allocation=False)
+        self.steps = alloc_raw_storage(LONG_SIZE * len(steps), track_allocation=False)
+        for i in range(len(dims)):
+            raw_storage_setitem(self.dims, LONG_SIZE * i, rffi.cast(rffi.LONG, dims[i]))
+        for i in range(len(steps)):
+            raw_storage_setitem(self.steps, LONG_SIZE * i, rffi.cast(rffi.LONG, steps[i]))
+        self.dims_steps_set = True
+
 W_GenericUFuncCaller.typedef = TypeDef("hiddenclass",
     __call__ = interp2app(W_GenericUFuncCaller.descr_call),
 )
 
-def set_dims_and_steps(obj, space, dims, steps):
-        if not isinstance(obj, W_GenericUFuncCaller):
-            raise OperationError(space.w_RuntimeError,
-                 space.wrap("set_dims_and_steps called inappropriately"))
-        if not isinstance(dims, list) or not isinstance(steps, list):
-            raise OperationError(space.w_RuntimeError,
-                 space.wrap("set_dims_and_steps called inappropriately"))
-        if len(dims) != len(step):
-            raise OperationError(space.w_RuntimeError,
-                 space.wrap("set_dims_and_steps called inappropriately"))
-        if self.dims is not None or self.steps is not None:
-            raise OperationError(space.w_RuntimeError,
-                 space.wrap("set_dims_and_steps called inappropriately"))
-        self.dims = alloc_raw_storage(LONG_SIZE * len(dims), track_allocation=False)
-        self.steps = alloc_raw_storage(LONG_SIZE * len(dims), track_allocation=False)
-        for d in dims:
-            raw_storage_setitem(self.dims, LONG_SIZE * i, rffi.cast(rffi.LONG, d))
-        for d in steps:
-            raw_storage_setitem(self.steps, LONG_SIZE * i, rffi.cast(rffi.LONG, d))
-
 GenericUfunc = lltype.FuncType([rffi.CArrayPtr(rffi.CCHARP), npy_intpp, npy_intpp,
                                       rffi.VOIDP], lltype.Void)


More information about the pypy-commit mailing list