[pypy-commit] pypy ufuncapi: add and start to test _parse_signature

mattip noreply at buildbot.pypy.org
Wed Aug 20 23:27:56 CEST 2014


Author: mattip <matti.picus at gmail.com>
Branch: ufuncapi
Changeset: r72939:ddd4e75d91b8
Date: 2014-08-20 22:59 +0300
http://bitbucket.org/pypy/pypy/changeset/ddd4e75d91b8/

Log:	add and start to test _parse_signature

diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py
--- a/pypy/module/micronumpy/support.py
+++ b/pypy/module/micronumpy/support.py
@@ -40,3 +40,110 @@
     if index < 0:
         index += size
     return index
+
+def _next_non_white_space(s, offset):
+    ret = offset
+    while ret < len(s) and (s[ret] == ' ' or s[ret] == '\t'):
+        ret += 1
+        if ret >= len(s):
+            break
+    return ret
+
+def _is_alpha_underscore(ch):
+    return (ch >= 'A' and ch <= 'Z') or (ch >= 'a' and ch <= 'z') or ch == '_'
+
+def _is_alnum_underscore(ch):
+    return _is_alpha_underscore(ch) or (ch >= '0' and ch <='9')
+
+def _parse_signature(space, ufunc, signature):
+    '''
+    rewritten from _parse_signature in numpy/core/src/umath/ufunc_object.c
+    it takes a signature like '(),()->()' or '(i)->(i)' or '(i,j),(j,k)->(i,k)'
+    and sets up the ufunc to handle the actual call appropriately
+
+    cpython numpy chops the dim names i,j,k out of the signature using pointers with
+    no copying, while faster than this code it seems like a marginally useful optimization.
+    We copy them out into var_names
+    '''
+    i = _next_non_white_space(signature, 0)
+    cur_arg = 0
+    cur_core_dim = 0 # index into ufunc.cor_dim_ixs
+    nd = 0           # number of dims of the current argument
+    var_names = {}
+    while i < len(signature):
+        # loop over input/output arguments
+        if cur_arg == ufunc.nin:
+            if signature[i:i+2] != '->':
+                raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+                    "expect '->'", i, signature)
+            i = _next_non_white_space(signature, i+2)    
+        # parse core dimensions of one argument,
+        # e.g. "()", "(i)", or "(i,j)"
+        if signature[i] != '(':
+            raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+                    "expect '('", i, signature)
+        i = _next_non_white_space(signature, i+1)
+        end_of_arg = signature.find(')', i)
+        if end_of_arg < 0:
+            raise oefmt(space.w_ValueError, '%s %d in "%s"',
+                    "could not find ')' after", i, signature)
+        if end_of_arg == i:
+            # no named arg, skip the next loop
+            next_comma = -1
+            i += 1
+        else:    
+            next_comma = signature.find(',', i, end_of_arg)
+            if next_comma < 0:
+                next_comma = end_of_arg
+        while next_comma > 0 and next_comma <= end_of_arg:
+            # loop over core dimensions
+            name_end = next_comma - 1
+            while signature[name_end] == ' ' or signature[name_end] == '\t':
+                name_end -= 1
+            var_name = signature[i:name_end + 1]
+            if not all([_is_alpha_underscore(s) for s in var_name]):
+                raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+                    "expect dimension name", i, signature)
+            if var_name not in var_names:
+                var_names[var_name] = ufunc.core_num_dim_ix
+                ufunc.core_num_dim_ix += 1
+            ufunc.core_dim_ixs[cur_core_dim] = var_names[var_name]
+            cur_core_dim += 1
+            nd += 1
+            i = next_comma
+            i = _next_non_white_space(signature, i + 1)
+            if signature[i] != ',' and signature[i] != ')' and signature[i] != '-':
+                raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+                    "expect ',' or ')' or '-'", i, signature)
+            if signature[i] == ',':
+                i = _next_non_white_space(signature, i + 1);
+                if signature[i] == ')':
+                    raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+                        "',' must not be followed by ')'", i, signature)
+            if end_of_arg <= i:
+                next_comma = -1
+                i = end_of_arg + 1
+            else:    
+                next_comma = signature.find(',', i, end_of_arg)
+                if next_comma < 0:
+                    next_comma = end_of_arg
+        ufunc.core_num_dims[cur_arg] = nd
+        ufunc.core_offsets[cur_arg] = cur_core_dim - nd
+        cur_arg += 1
+        nd = 0
+        if i < len(signature):
+            i = _next_non_white_space(signature, i)
+        if cur_arg != ufunc.nin and cur_arg != ufunc.nargs:
+            # The list of input arguments (or output arguments) was
+            # only read partially
+            if signature[i] != ',':
+                raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+                    "expect ','", i, signature)
+            i = _next_non_white_space(signature, i + 1);
+    if cur_arg != ufunc.nargs:
+        raise oefmt(space.w_ValueError, '%s at %d in "%s"',
+            "incomplete signature: not all arguments found", i, signature)
+    ufunc.core_dim_ixs = ufunc.core_dim_ixs[:cur_core_dim]    
+    if cur_core_dim == 0:
+        ufunc.core_enabled = 0
+    return 0 # for historical reasons, any failures will raise   
diff --git a/pypy/module/micronumpy/test/test_support.py b/pypy/module/micronumpy/test/test_support.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/micronumpy/test/test_support.py
@@ -0,0 +1,30 @@
+from pypy.module.micronumpy import support
+from pypy.module.micronumpy.ufuncs import W_UfuncGeneric
+from pypy.module.micronumpy.test.test_base import BaseNumpyAppTest
+
+class TestParseSignatureDirect(BaseNumpyAppTest):
+    def test_signature_basic(self):
+        space = self.space
+        funcs = [None]
+        name = 'dummy ufunc'
+        identity = None
+        dtypes = [int, int, int]
+        
+        nin = 2
+        nout = 1
+        signature = '(), () -> (  ) '
+        ufunc = W_UfuncGeneric(space, funcs, name, identity, nin, nout, dtypes, signature)
+        # make sure no attributes are added
+        attribs = set(ufunc.__dict__.keys())
+        support._parse_signature(space, ufunc, ufunc.signature)
+        new_attribs = set(ufunc.__dict__.keys())
+        assert attribs == new_attribs
+        assert sum(ufunc.core_num_dims) == 0
+        assert ufunc.core_enabled == 0
+
+        nin = 2
+        nout = 1
+        signature = '(i),(i)->()'
+        ufunc = W_UfuncGeneric(space, funcs, name, identity, nin, nout, dtypes, signature)
+        support._parse_signature(space, ufunc, ufunc.signature)
+        assert ufunc.core_enabled == 1
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
@@ -10,6 +10,7 @@
 from pypy.module.micronumpy.base import convert_to_array, W_NDimArray
 from pypy.module.micronumpy.ctors import numpify
 from pypy.module.micronumpy.strides import shape_agreement
+from pypy.module.micronumpy.support import _parse_signature
 
 
 def done_if_true(dtype, val):
@@ -490,8 +491,10 @@
 
     If dtypes == 'match', only one argument is provided and the output dtypes
     will match the input dtype (not cpython numpy compatible)
+
+    This is the parallel to PyUFuncOjbect, see include/numpy/ufuncobject.h
     '''
-    _immutable_fields_ = ["funcs", "dtypes", "data"]
+    _immutable_fields_ = ["funcs", "dtypes", "data", "match_dtypes"]
 
     def __init__(self, space, funcs, name, identity, nin, nout, dtypes, signature, match_dtypes=False):
         # XXX make sure funcs, signature, dtypes, nin, nout are consistent
@@ -515,6 +518,12 @@
                 "generic ufunc with %d functions, %d arguments, but %d dtypes",
                 len(funcs), self.nargs, len(dtypes))
         self.signature = signature
+        #These will be filled in by _parse_signature
+        self.core_enabled = True    # False for scalar ufunc, True for generalized ufunc
+        self.core_num_dim_ix = 0 # number of distinct dimention names in signature
+        self.core_num_dims = [0] * self.nargs  # number of core dimensions of each nargs
+        self.core_offsets = [0] * self.nargs
+        self.core_dim_ixs = [0] * len(signature)
 
     def reduce(self, space, w_obj, w_axis, keepdims=False, out=None, dtype=None,
                cumulative=False):
@@ -1027,6 +1036,7 @@
 
     w_ret = W_UfuncGeneric(space, func, name, identity, nin, nout, dtypes, signature,
                                 match_dtypes=match_dtypes)
+    _parse_signature(space, w_ret, w_ret.signature)
     if doc:
         w_ret.w_doc = space.wrap(doc)
     return w_ret


More information about the pypy-commit mailing list