8000 WIP: ENH: Expand gufunc signature to allow flexible dimension specs by mattip · Pull Request #11132 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

WIP: ENH: Expand gufunc signature to allow flexible dimension specs #11132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 28 additions & 16 deletions doc/source/reference/c-api.generalized-ufuncs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Dimension Index
enumerates the dimension names according to the order of the first
occurrence of each name in the signature.

.. _details-of-signature:

Details of Signature
--------------------
Expand All @@ -126,9 +127,10 @@ The formal syntax of signatures is as follows::
<Output arguments> ::= <Argument list>
<Argument list> ::= nil | <Argument> | <Argument> "," <Argument list>
<Argument> ::= "(" <Core dimension list> ")"
<Core dimension list> ::= nil | <Dimension name> |
<Dimension name> "," <Core dimension list>
<Dimension name> ::= valid Python variable name
<Core dimension list> ::= nil | <Core dimension name> |
<Core dimension name> "," <Core dimension list>
<Core dimension name> ::= valid Python variable name |
valid Python variable name ?


Notes:
Expand All @@ -138,22 +140,32 @@ Notes:
Each dimension name typically corresponds to one level of looping in the
elementary function's implementation.
#. White spaces are ignored.
#. The name can be suffixed with a question mark, this make the dimension a
core dimension only if it exists on the input or output, otherwise 1 is used.

Here are some examples of signatures:

+-------------+------------------------+-----------------------------------+
| add | ``(),()->()`` | |
+-------------+------------------------+-----------------------------------+
| inner1d | ``(i),(i)->()`` | |
+-------------+------------------------+-----------------------------------+
| sum1d | ``(i)->()`` | |
+-------------+------------------------+-----------------------------------+
| dot2d | ``(m,n),(n,p)->(m,p)`` | matrix multiplication |
+-------------+------------------------+-----------------------------------+
| outer_inner | ``(i,t),(j,t)->(i,j)`` | inner over the last dimension, |
| | | outer over the second to last, |
| | | and loop/broadcast over the rest. |
+-------------+------------------------+-----------------------------------+
+-------------+----------------------------+-----------------------------------+
| add | ``(),()->()`` | |
+-------------+----------------------------+-----------------------------------+
| inner1d | ``(i),(i)->()`` | |
+-------------+----------------------------+-----------------------------------+
| sum1d | ``(i)->()`` | |
+-------------+----------------------------+-----------------------------------+
| dot2d | ``(m,n),(n,p)->(m,p)`` | matrix multiplication |
+-------------+----------------------------+-----------------------------------+
| dot2d | ``(n),(n,p)->(p)`` | vector-matrix multiplication |
+-------------+----------------------------+-----------------------------------+
| dot2d | ``(n),(n)->()`` | vector-vector multiplication |
+-------------+----------------------------+-----------------------------------+
| dot2d | ``(m,n),(n)->(m)`` | matrix-vector multiplication |
+-------------+----------------------------+-----------------------------------+
| dot2d | ``(m?,n),(n,p?)->(m?,p?)`` | all four of the above at once |
+-------------+----------------------------+-----------------------------------+
| outer_inner | ``(i,t),(j,t)->(i,j)`` | inner over the last dimension, |
| | | outer over the second to last, |
| | | and loop/broadcast over the rest. |
+-------------+----------------------------+-----------------------------------+

C-API for implementing Elementary Functions
-------------------------------------------
Expand Down
21 changes: 14 additions & 7 deletions doc/source/reference/c-api.ufunc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,18 @@ Functions
the corresponding 1-d loop function in the func array.

:param types:
Must be of length (*nin* + *nout*) \* *ntypes*, and it
contains the data-types (built-in only) that the corresponding
function in the *func* array can deal with.
Int8 of length `(nin + nout) * ntypes` It encodes the :ref:`dtype.num`
(built-in only) that the corresponding function in the `func` array
accepts. For a ufunc with two `ntypes`, one `nin` and one `nout` where
the first function accepts and returns `int32` and the second accepts
and returns `int64`, `types` would be `\05\05\07\07` since `int32.num`
is 5 and `int64.num` is 7.

:ref:`casting-rules` will be used at runtime to find the first `func` callable
by the input/output provided.

:param ntypes:
How many different data-type "signatures" the ufunc has implemented.
How many different data-type-specific functions the ufunc has implemented.

:param nin:
The number of inputs to this operation.
Expand Down Expand Up @@ -129,10 +135,11 @@ Functions
int nin, int nout, int identity, char* name, char* doc, int unused, char *signature)

This function is very similar to PyUFunc_FromFuncAndData above, but has
an extra *signature* argument, to define generalized universal functions.
an extra *signature* argument, to define a
:ref:`generalized universal functions <c-api.generalized-ufuncs>`.
Similarly to how ufuncs are built around an element-by-element operation,
gufuncs are around subarray-by-subarray operations, the signature defining
the subarrays to operate on.
gufuncs are around subarray-by-subarray operations, the
:ref:`signature <details-of-signature>` defining the subarrays to operate on.

:param signature:
The signature for the new gufunc. Setting it to NULL is equivalent
Expand Down
4 changes: 2 additions & 2 deletions doc/source/reference/ufuncs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ advanced usage and will not typically be used.
provided by the **types** attribute of the ufunc object. For backwards
compatibility this argument can also be provided as *sig*, although
the long form is preferred. Note that this should not be confused with
the generalized ufunc signature that is stored in the **signature**
attribute of the of the ufunc object.
the generalized ufunc :ref:`signature <details-of-signature>` that is
stored in the **signature** attribute of the of the ufunc object.

*extobj*

Expand Down
1 change: 1 addition & 0 deletions doc/source/user/c-info.ufunc-tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,7 @@ PyUFunc_FromFuncAndData Specification
What follows is the full specification of PyUFunc_FromFuncAndData, which
automatically generates a ufunc from a C function with the correct signature.

.. seealso:: :c:func:`PyUFunc_FromFuncAndDataAndSignature`

.. c:function:: PyObject *PyUFunc_FromFuncAndData( \
PyUFuncGenericFunction* func, void** data, char* types, int ntypes, \
Expand Down
27 changes: 15 additions & 12 deletions numpy/core/code_generators/generate_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, suffix):
self.suffix = suffix

class TypeDescription(object):
"""Type signature for a ufunc.
"""Types signature for a ufunc

Attributes
----------
Expand Down Expand Up @@ -62,7 +62,7 @@ def __init__(self, type, f=None, in_=None, out=None, astype=None, simd=None):
self.out = out
self.simd = simd

def finish_signature(self, nin, nout):
def finish_types(self, nin, nout):
if self.in_ is None:
self.in_ = self.type * nin
assert len(self.in_) == nin
Expand Down Expand Up @@ -122,21 +122,23 @@ class Ufunc(object):
identity : identity element for a two-argument function
docstring : docstring for the ufunc
type_descriptions : list of TypeDescription objects
signature: optional string description of input output shape relationship
"""
def __init__(self, nin, nout, identity, docstring, typereso,
*type_descriptions):
*type_descriptions, **kwds):
self.nin = nin
self.nout = nout
if identity is None:
identity = None_
self.identity = identity
self.docstring = docstring
self.typereso = typereso
self.signature = kwds.get('signature', 'NULL')
self.type_descriptions = []
for td in type_descriptions:
self.type_descriptions.extend(td)
for td in self.type_descriptions:
td.finish_signature(self.nin, self.nout)
td.finish_types(self.nin, self.nout)

# String-handling utilities to avoid locale-dependence.

Expand Down Expand Up @@ -182,7 +184,7 @@ def english_upper(s):
#name: [string of chars for which it is defined,
# string of characters using func interface,
# tuple of strings giving funcs for data,
# (in, out), or (instr, outstr) giving the signature as character codes,
# (in, out), or (instr, outstr) the types argument as character codes,
# identity,
# docstring,
# output specification (optional)
Expand Down Expand Up @@ -256,7 +258,7 @@ def english_upper(s):
break

# This dictionary describes all the ufunc implementations, generating
# all the function names and their corresponding ufunc signatures. TD is
# all the function names and their corresponding ufunc types. TD is
# an object which expands a list of character codes into an array of
# TypeDescriptions.
defdict = {
Expand Down Expand Up @@ -934,7 +936,7 @@ def indent(st, spaces):
'O': 'OO_O',
'P': 'OO_O_method'}
#for each name
# 1) create functions, data, and signature
# 1) create functions, data, types, and signature
# 2) fill in functions and data in InitOperators
# 3) add function.

Expand Down Expand Up @@ -1022,7 +1024,7 @@ def make_arrays(funcdict):
% (name, funcnames))
code1list.append("static void * %s_data[] = {%s};"
% (name, datanames))
code1list.append("static char %s_signatures[] = {%s};"
code1list.append("static char %s_types[] = {%s};"
% (name, signames))
return "\n".join(code1list), "\n".join(code2list)

Expand All @@ -1047,17 +1049,18 @@ def make_ufuncs(funcdict):
# do not play well with \n
docstring = '\\n\"\"'.join(docstring.split(r"\n"))
fmt = textwrap.dedent("""\
f = PyUFunc_FromFuncAndData(
{name}_functions, {name}_data, {name}_signatures, {nloops},
f = PyUFunc_FromFuncAndDataAndSignature(
{name}_functions, {name}_data, {name}_types, {nloops},
{nin}, {nout}, {identity}, "{name}",
"{doc}", 0
"{doc}", 0, {signature}
);
if (f == NULL) {{
return -1;
}}""")
mlist.append(fmt.format(
name=name, nloops=len(uf.type_descriptions),
nin=uf.nin, nout=uf.nout, identity=uf.identity, doc=docstring
nin=uf.nin, nout=uf.nout, identity=uf.identity, doc=docstring,
signature=uf.signature,
))
if uf.typereso is not None:
mlist.append(
Expand Down
14 changes: 11 additions & 3 deletions numpy/core/include/numpy/ufuncobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ typedef struct _tagPyUFuncObject {
/* The number of elements in 'functions' and 'data' */
int ntypes;

/* Used to be unused field 'check_return' */
int reserved1;
/* Used to be unused field 'check_return', repurposed in 1.16 */
int version;

/* The name of the ufunc */
const char *name;
Expand Down Expand Up @@ -167,7 +167,7 @@ typedef struct _tagPyUFuncObject {
int *core_dim_ixs;
/*
* positions of 1st core dimensions of each
* argument in core_dim_ixs
* argument in core_dim_ixs, equivalent to cumsum(core_num_dims)
*/
int *core_offsets;
/* signature string for printing purpose */
Expand Down Expand Up @@ -209,6 +209,14 @@ typedef struct _tagPyUFuncObject {
* set by nditer object.
*/
npy_uint32 iter_flags;

/* New in version 1 and above */

/*
* for each core_num_dim_ix, 1 for flexible (signature has ?) 0 otherwise
*/
int *core_dim_flexible;
Copy link
Contributor
@mhvk mhvk May 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation suggestion: I think we might as well make this a set of flags, so that we can re-use them for future expansions.

In fact, one could then add not just a flag UFUNC_COREDIM_FLEXIBLE but also a UFUNC_COREDIM_FIXED - for that case, core_num_dim_idx core_dim_ixs could just be set to the size, since there is no need to track the index.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going ahead and try this - I guess one can see it as my contribution to the BIDS sprint!


} PyUFuncObject;

#include "arrayobject.h"
Expand Down
11 changes: 11 additions & 0 deletions numpy/core/src/multiarray/scalartypes.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,13 @@ gentype_@name@(PyObject *m1, PyObject *m2)
/**end repeat**/
#endif

static PyObject *
gentype21_not_implemented(PyObject *m1, PyObject *m2)
{
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}

/* Get a nested slot, or NULL if absent */
#define GET_NESTED_SLOT(type, group, slot) \
((type)->group == NULL ? NULL : (type)->group->slot)
Expand Down Expand Up @@ -1159,6 +1166,10 @@ static PyNumberMethods gentype_as_number = {
0, /*nb_inplace_floor_divide*/
0, /*nb_inplace_true_divide*/
(unaryfunc)NULL, /*nb_index*/
#if PY_VERSION_HEX >= 0x03050000
(binaryfunc)gentype21_not_implemented, /*nb_matrix_multiply*/
0, /*nb_inplace_matrix_multiply*/
#endif
};


Expand Down
42 changes: 35 additions & 7 deletions numpy/core/src/umath/_umath_tests.c.src
1241
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,11 @@ addUfuncs(PyObject *dictionary) {
static PyObject *
UMath_Tests_test_signature(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
int nin, nout;
PyObject *signature, *sig_str;
PyObject *f;
int nin, nout, i, core_num_ixs=0;
PyObject *signature=NULL, *sig_str=NULL;
PyUFuncObject *f=NULL;
PyObject *core_num_dims=NULL, *core_dim_ixs=NULL;
PyObject *core_dim_flexible=NULL;
int core_enabled;

if (!PyArg_ParseTuple(args, "iiO", &nin, &nout, &signature)) return NULL;
Expand All @@ -334,17 +336,43 @@ UMath_Tests_test_signature(PyObject *NPY_UNUSED(dummy), PyObject *args)
return NULL;
}

f = PyUFunc_FromFuncAndDataAndSignature(NULL, NULL, NULL,
f = (PyUFuncObject*)PyUFunc_FromFuncAndDataAndSignature(NULL, NULL, NULL,
0, nin, nout, PyUFunc_None, "no name",
"doc:none",
1, PyString_AS_STRING(sig_str));
if (sig_str != signature) {
Py_DECREF(sig_str);
}
if (f == NULL) return NULL;
core_enabled = ((PyUFuncObject*)f)->core_enabled;
if (f == NULL) goto error;
core_enabled = f->core_enabled;
core_num_dims = PyTuple_New(f->nargs);
if (core_num_dims == NULL) goto error;
for (i=0; i<f->nargs; i++) {
PyObject * val = PyLong_FromLong(f->core_num_dims[i]);
PyTuple_SET_ITEM(core_num_dims, i, val);
core_num_ixs += f->core_num_dims[i];
}
core_dim_ixs = PyTuple_New(core_num_ixs);
if (core_num_dims == NULL) goto error;
for (i=0; i<core_num_ixs; i++) {
PyObject * val = PyLong_FromLong(f->core_dim_ixs[i]);
PyTuple_SET_ITEM(core_dim_ixs, i, val);
}
core_dim_flexible = PyTuple_New(f->core_num_dim_ix);
if (core_dim_flexible == NULL) goto error;
for (i=0; i<f->core_num_dim_ix; i++) {
PyObject * val = PyLong_FromLong(f->core_dim_flexible[i]);
PyTuple_SET_ITEM(core_dim_flexible, i, val);
}
Py_DECREF(f);
return Py_BuildValue("i", core_enabled);
return Py_BuildValue("iOOO", core_enabled, core_num_dims,
core_dim_ixs, core_dim_flexible);
error:
Py_XDECREF(f);
Py_XDECREF(core_num_dims);
Py_XDECREF(core_dim_ixs);
Py_XDECREF(core_dim_flexible);
return NULL;
}

static PyMethodDef UMath_TestsMethods[] = {
Expand Down
Loading
0