[pypy-commit] pypy ufuncapi: different approach - implement frompyfunc, extended for pypy-only kwargs.
mattip
noreply at buildbot.pypy.org
Mon Jun 9 23:45:00 CEST 2014
Author: mattip <matti.picus at gmail.com>
Branch: ufuncapi
Changeset: r72010:9f583aac6370
Date: 2014-06-10 00:32 +0300
http://bitbucket.org/pypy/pypy/changeset/9f583aac6370/
Log: different approach - implement frompyfunc, extended for pypy-only
kwargs. then implement app-level calls with cffi functions to
frompyfunc
diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py
--- a/pypy/module/micronumpy/__init__.py
+++ b/pypy/module/micronumpy/__init__.py
@@ -34,6 +34,7 @@
appleveldefs = {}
interpleveldefs = {
'FLOATING_POINT_SUPPORT': 'space.wrap(1)',
+ 'frompyfunc': 'ufuncs.frompyfunc',
}
# ufuncs
for exposed, impl in [
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
@@ -2,6 +2,8 @@
from pypy.module.micronumpy.ufuncs import (find_binop_result_dtype,
find_unaryop_result_dtype)
from pypy.module.micronumpy.descriptor import get_dtype_cache
+from pypy.interpreter.gateway import interp2app
+from pypy.conftest import option
try:
@@ -144,28 +146,43 @@
# promote bools, happens with sign ufunc
assert find_unaryop_result_dtype(space, bool_dtype, promote_bools=True) is int8_dtype
-class TestUfuncFromCFunc(object):
- def test_fromcfunc(self,space):
- if not cfuncs:
- skip('no cffi available')
- from pypy.module.micronumpy.ufuncs import ufunc_from_func_and_data_and_signature as from_cfunc
- from pypy.module.micronumpy.ctors import array
- int32_dtype = get_dtype_cache(space).w_int32dtype
- float64_dtype = get_dtype_cache(space).w_float64dtype
- data = ffi.new('char *[2]')
- func = from_cfunc([cfuncs.double_times2, cfuncs.int_times2], data,
- [float64_dtype, float64_dtype, int32_dtype, int32_dtype],
- 1, 1, 0, 'times2', 'times2_doc', 0, '()->()',
- )
- def get(i):
- return w_result.getitem(space, [i]).value
- for d in [int32_dtype, float64_dtype]:
- w_array = array(space, space.wrap(range(10)), w_dtype=d)
- w_result = func.call(space, [w_array])
- for i in 10:
- assert get(i) == 2*i
+class AppTestUfuncs(BaseNumpyAppTest):
+ def setup_class(cls):
+ BaseNumpyAppTest.setup_class.im_func(cls)
+ if cfuncs:
+ def int_times2(space, __args__):
+ args, kwargs = __args__.unpack()
+ arr = map(space.unwrap, args)
+ # Assume arr is contiguous
+ addr = cfuncs.new('char *[2]')
+ addr[0] = arr[0].data
+ addr[1] = arr[1].data
+ dims = cfuncs.new('int *[1]')
+ dims[0] = arr[0].size
+ steps = cfuncs.new('int *[1]')
+ steps[0] = arr[0].strides[-1]
+ cfuncs.int_times2(addr, dims, steps, 0)
+ def double_times2(space, __args__):
+ args, kwargs = __args__.unpack()
+ arr = map(space.unwrap, args)
+ # Assume arr is contiguous
+ addr = cfuncs.new('char *[2]')
+ addr[0] = arr[0].data
+ addr[1] = arr[1].data
+ dims = cfuncs.new('int *[1]')
+ dims[0] = arr[0].size
+ steps = cfuncs.new('int *[1]')
+ steps[0] = arr[0].strides[-1]
+ cfuncs.double_times2(addr, dims, steps, 0)
+ if option.runappdirect:
+ times2 = cls.space.wrap([double_times2, int_times2])
+ else:
+ times2 = cls.space.wrap([interp2app(double_times2),
+ interp2app(int_times2)])
+ else:
+ times2 = None
+ cls.w_times2 = cls.space.wrap(times2)
-class AppTestUfuncs(BaseNumpyAppTest):
def test_constants(self):
import numpy as np
assert np.FLOATING_POINT_SUPPORT == 1
@@ -180,27 +197,46 @@
raises(TypeError, ufunc)
def test_frompyfunc(self):
- try:
- from numpy import frompyfunc
- except ImportError:
- skip('frompyfunc not available')
from numpy import ufunc, frompyfunc, arange, dtype
def adder(a, b):
return a+b
- myufunc = frompyfunc(adder, 2, 1)
+ try:
+ myufunc = frompyfunc(adder, 2, 1)
+ int_func22 = frompyfunc(int, 2, 2)
+ int_func12 = frompyfunc(int, 1, 2)
+ retype = dtype(object)
+ except NotImplementedError as e:
+ assert 'object' in str(e)
+ # Use pypy specific extension for out_dtype
+ myufunc = frompyfunc(adder, 2, 1, out_dtype='match')
+ int_func22 = frompyfunc(int, 2, 2, out_dtype='match')
+ int_func12 = frompyfunc(int, 1, 2, out_dtype='match')
+ retype = dtype(int)
assert isinstance(myufunc, ufunc)
res = myufunc(arange(10), arange(10))
- assert res.dtype == dtype(object)
+ assert res.dtype == retype
assert all(res == arange(10) + arange(10))
raises(TypeError, frompyfunc, 1, 2, 3)
- int_func22 = frompyfunc(int, 2, 2)
raises (ValueError, int_func22, arange(10))
- int_func12 = frompyfunc(int, 1, 2)
res = int_func12(arange(10))
assert len(res) == 2
assert isinstance(res, tuple)
assert (res[0] == arange(10)).all()
+ def test_from_cffi_func(self):
+ import sys
+ if '__pypy__' not in sys.builtin_module_names:
+ skip('pypy-only test')
+ from numpy import frompyfunc, dtype, arange
+ if self.times2 is None:
+ skip('cffi not available')
+ ufunc = frompyfunc(self.times2, 1, 1, signature='()->()',
+ dtypes=[dtype(float), dtype(float), dtype(int), dtype(int)],
+ )
+ f = arange(10, dtype=int)
+ f2 = ufunc(f)
+ assert f2
+
def test_ufunc_attrs(self):
from numpy import add, multiply, sin
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
@@ -1,7 +1,8 @@
from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
-from pypy.interpreter.gateway import interp2app, unwrap_spec
+from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty
+from pypy.interpreter.argument import Arguments
from rpython.rlib import jit
from rpython.rlib.rarithmetic import LONG_BIT, maxint
from rpython.tool.sourcetools import func_with_new_name
@@ -464,25 +465,36 @@
class W_UfuncGeneric(W_Ufunc):
+ '''
+ Handle a number of python functions, each with a signature and dtypes.
+ The signature can specify how to create the inner loop, i.e.
+ (i,j),(j,k)->(i,k) for a dot-like matrix multiplication, and the dtypes
+ can specify the input, output args for the function. When called, the actual
+ function used will be resolved by examining the input arg's dtypes.
+
+ If dtypes == 'match', only one argument is provided and the output dtypes
+ will match the input dtype (not cpython numpy compatible)
+ '''
_immutable_fields_ = ["funcs", "signature", "nin", "nout", "nargs",
"dtypes", "data"]
- def __init__(self, funcs, name, identity, data, nin, nout, dtypes, signature):
+ def __init__(self, space, funcs, name, identity, nin, nout, dtypes, signature):
# XXX make sure funcs, signature, dtypes, nin, nout are consistent
# These don't matter, we use the signature and dtypes for determining
# output dtype
promote_to_largest = promote_to_float = promote_bools = False
- int_only = allow_bool = allow_complex = complex_to_float = False
+ allow_bool = allow_complex = True
+ int_only = complex_to_float = False
W_Ufunc.__init__(self, name, promote_to_largest, promote_to_float, promote_bools,
identity, int_only, allow_bool, allow_complex, complex_to_float)
self.funcs = funcs
self.dtypes = dtypes
self.nin = nin
self.nout = nout
- self.data = data
self.nargs = nin + max(nout, 1) # ufuncs can always be called with an out=<> kwarg
- if len(dtypes) % len(funcs) != 0 or len(dtypes) / len(funcs) != self.nargs:
+ if dtypes != 'match' and (len(dtypes) % len(funcs) != 0 or
+ len(dtypes) / len(funcs) != self.nargs):
raise oefmt(space.w_ValueError,
"generic ufunc with %d functions, %d arguments, but %d dtypes",
len(funcs), self.nargs, len(dtypes))
@@ -494,8 +506,6 @@
def call(self, space, args_w):
#from pypy.module._cffi_backend import newtype, func as _func
- from rpython.rlib.rawstorage import alloc_raw_storage, raw_storage_setitem
- from rpython.rtyper.lltypesystem import rffi, lltype
out = None
inargs = []
if len(args_w) < self.nin:
@@ -508,34 +518,17 @@
for i in range(min(self.nout, len(args_w)-self.nin)):
out = args_w[i+self.nin]
if space.is_w(out, space.w_None) or out is None:
- outargs.append(None)
+ continue
else:
if not isinstance(out, W_NDimArray):
raise oefmt(space.w_TypeError,
'output arg %d must be an array, not %s', i+self.nin, str(args_w[i+self.nin]))
- outargs.append(out)
+ outargs[i] = out
index = self.type_resolver(space, inargs, outargs)
self.alloc_outargs(space, index, inargs, outargs)
- func, dims, steps = self.prep_call(space, index, inargs, outargs)
- psize = rffi.sizeof(rffi.VOIDP)
- lsize = rffi.sizeof(rffi.LONG)
- data = alloc_raw_storage(psize*self.nargs)
- dims_p = alloc_raw_storage(lsize * len(dims))
- steps_p = alloc_raw_storage(lsize * len(steps))
- for i in range(len(inargs)):
- pdata = inargs[i].implementation.get_storage_as_int(space)
- raw_storage_setitem(data, i * psize, pdata)
- for j in range(len(outargs)):
- pdata = outargs[j].implementation.get_storage_as_int(space)
- raw_storage_setitem(data, (i + j) * psize, pdata)
- for i in range(len(dims)):
- raw_storage_setitem(dims_p, i * lsize, dims[i])
- raw_storage_setitem(steps_p, i * lsize, steps[i])
- print 'calling',func, hex(rffi.cast(lltype.Signed, func))
- func(rffi.cast(rffi.CArrayPtr(rffi.CCHARP), data), rffi.cast(rffi.LONGP, dims_p), rffi.cast(rffi.LONGP, steps_p), rffi.cast(rffi.VOIDP, 0))
- if len(outargs)>1:
- return outargs
- return outargs[0]
+ # XXX handle inner-loop indexing
+ # XXX JIT_me
+ raise oefmt(space.w_NotImplementedError, 'not implemented yet')
def type_resolver(self, space, index, outargs):
# Find a match for the inargs.dtype in self.dtypes, like
@@ -887,7 +880,108 @@
def get(space):
return space.fromcache(UfuncState)
-def ufunc_from_func_and_data_and_signature(funcs, data, dtypes, nin, nout,
- identity, name, doc, check_return, signature):
- return W_UfuncGeneric(funcs, name, identity, data, nin, nout, dtypes, signature)
- pass
+ at unwrap_spec(nin=int, nout=int, signature=str, w_identity=WrappedDefault(None),
+ name=str, doc=str)
+def frompyfunc(space, w_func, nin, nout, w_dtypes=None, signature='',
+ w_identity=None, name='', doc=''):
+ ''' frompyfunc(func, nin, nout) #cpython numpy compatible
+ frompyfunc(func, nin, nout, dtypes=None, signature='',
+ identity=None, name='', doc='')
+
+ Takes an arbitrary Python function and returns a ufunc.
+
+ Can be used, for example, to add broadcasting to a built-in Python
+ function (see Examples section).
+
+ Parameters
+ ----------
+ func : Python function object
+ An arbitrary Python function or list of functions (if dtypes is specified).
+ nin : int
+ The number of input arguments.
+ nout : int
+ The number of arrays returned by `func`.
+ dtypes: None or [dtype, ...] of the input, output args for each function,
+ or 'match' to force output to exactly match input dtype
+ signature*: str, default=''
+ The mapping of input args to output args, defining the
+ inner-loop indexing
+ identity*: None (default) or int
+ For reduce-type ufuncs, the default value
+ name: str, default=''
+ doc: str, default=''
+
+ only one of out_dtype or signature may be specified
+
+ Returns
+ -------
+ out : ufunc
+ Returns a Numpy universal function (``ufunc``) object.
+
+ Notes
+ -----
+ If the signature and out_dtype are both missing, the returned ufunc always
+ returns PyObject arrays (cpython numpy compatability).
+
+ Examples
+ --------
+ Use frompyfunc to add broadcasting to the Python function ``oct``:
+
+ >>> oct_obj_array = np.frompyfunc(oct, 1, 1)
+ >>> oct_obj_array(np.array((10, 30, 100)))
+ array([012, 036, 0144], dtype=object)
+ >>> np.array((oct(10), oct(30), oct(100))) # for comparison
+ array(['012', '036', '0144'],
+ dtype='|S4')
+ >>> oct_array = np.frompyfunc(oct, 1, 1, out_dtype=str)
+ >>> oct_obj_array(np.array((10, 30, 100)))
+ array([012, 036, 0144], dtype='|S4')
+ '''
+ if (space.isinstance_w(w_func, space.w_tuple) or
+ space.isinstance_w(w_func, space.w_list)):
+ func = space.listview(w_func)
+ for w_f in func:
+ if not space.is_true(space.callable(w_f)):
+ raise oefmt(space.w_TypeError, 'func must be callable')
+ else:
+ if not space.is_true(space.callable(w_func)):
+ raise oefmt(space.w_TypeError, 'func must be callable')
+ func = [w_func]
+
+ if space.is_none(w_dtypes) and not signature:
+ raise oefmt(space.w_NotImplementedError,
+ 'object dtype requested but not implemented')
+ #dtypes=[descriptor.get_dtype_cache(space).w_objectdtype]
+ elif space.isinstance_w(w_dtypes, space.w_str):
+ if not space.str_w(w_dtypes) == 'match':
+ raise oefmt(space.w_ValueError,
+ 'unknown out_dtype value "%s"', space.str_w(w_dtypes))
+ dtypes = 'match'
+ elif (space.isinstance_w(w_dtypes, space.w_tuple) or
+ space.isinstance_w(w_dtypes, space.w_list)):
+ dtypes = space.listview(w_dtypes)
+ for i in range(len(dtypes)):
+ dtypes[i] = descriptor.decode_w_dtype(space, dtypes[i])
+ else:
+ raise oefmt(space.w_ValueError,
+ 'dtypes must be None or a list of dtypes')
+
+ if space.is_none(w_identity):
+ identity = None
+ elif space.isinstance_w(w_identity, space.int_w):
+ identity = space.int_w(w_identity)
+ else:
+ raise oefmt(space.w_ValueError,
+ 'identity must be 0, 1, or None')
+ if nin==1 and nout==1 and dtypes == 'match':
+ w_ret = W_Ufunc1(func[0], name)
+ elif nin==2 and nout==1 and dtypes == 'match':
+ def _func(calc_dtype, w_left, w_right):
+ arglist = space.wrap([w_left, w_right])
+ return space.call_args(func[0], Arguments.frompacked(space, arglist))
+ w_ret = W_Ufunc2(_func, name)
+ else:
+ w_ret = W_UfuncGeneric(space, func, name, identity, nin, nout, dtypes, signature)
+ if doc:
+ w_ret.w_doc = space.wrap(doc)
+ return w_ret
More information about the pypy-commit
mailing list