mirror of
https://github.com/unitedstates/congress-legislators.git
synced 2026-05-17 01:04:12 -04:00
1047 lines
32 KiB
ReStructuredText
1047 lines
32 KiB
ReStructuredText
**********************
|
|
Writing your own ufunc
|
|
**********************
|
|
|
|
| I have the Power!
|
|
| --- *He-Man*
|
|
|
|
|
|
.. _`sec:Creating-a-new`:
|
|
|
|
Creating a new universal function
|
|
=================================
|
|
|
|
.. index::
|
|
pair: ufunc; adding new
|
|
|
|
Before reading this, it may help to familiarize yourself with the basics
|
|
of C extensions for Python by reading/skimming the tutorials in Section 1
|
|
of `Extending and Embedding the Python Interpreter
|
|
<http://docs.python.org/extending/index.html>`_ and in `How to extend
|
|
Numpy <http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html>`_
|
|
|
|
The umath module is a computer-generated C-module that creates many
|
|
ufuncs. It provides a great many examples of how to create a universal
|
|
function. Creating your own ufunc that will make use of the ufunc
|
|
machinery is not difficult either. Suppose you have a function that
|
|
you want to operate element-by-element over its inputs. By creating a
|
|
new ufunc you will obtain a function that handles
|
|
|
|
- broadcasting
|
|
|
|
- N-dimensional looping
|
|
|
|
- automatic type-conversions with minimal memory usage
|
|
|
|
- optional output arrays
|
|
|
|
It is not difficult to create your own ufunc. All that is required is
|
|
a 1-d loop for each data-type you want to support. Each 1-d loop must
|
|
have a specific signature, and only ufuncs for fixed-size data-types
|
|
can be used. The function call used to create a new ufunc to work on
|
|
built-in data-types is given below. A different mechanism is used to
|
|
register ufuncs for user-defined data-types.
|
|
|
|
In the next several sections we give example code that can be
|
|
easily modified to create your own ufuncs. The examples are
|
|
successively more complete or complicated versions of the logit
|
|
function, a common function in statistical modeling. Logit is also
|
|
interesting because, due to the magic of IEEE standards (specifically
|
|
IEEE 754), all of the logit functions created below
|
|
automatically have the following behavior.
|
|
|
|
>>> logit(0)
|
|
-inf
|
|
>>> logit(1)
|
|
inf
|
|
>>> logit(2)
|
|
nan
|
|
>>> logit(-2)
|
|
nan
|
|
|
|
This is wonderful because the function writer doesn't have to
|
|
manually propagate infs or nans.
|
|
|
|
.. _`sec:Non-numpy-example`:
|
|
|
|
Example Non-ufunc extension
|
|
===========================
|
|
|
|
.. index::
|
|
pair: ufunc; adding new
|
|
|
|
For comparison and general edificaiton of the reader we provide
|
|
a simple implementation of a C extension of logit that uses no
|
|
numpy.
|
|
|
|
To do this we need two files. The first is the C file which contains
|
|
the actual code, and the second is the setup.py file used to create
|
|
the module.
|
|
|
|
.. code-block:: c
|
|
|
|
#include <Python.h>
|
|
#include <math.h>
|
|
|
|
/*
|
|
* spammodule.c
|
|
* This is the C code for a non-numpy Python extension to
|
|
* define the logit function, where logit(p) = log(p/(1-p)).
|
|
* This function will not work on numpy arrays automatically.
|
|
* numpy.vectorize must be called in python to generate
|
|
* a numpy-friendly function.
|
|
*
|
|
* Details explaining the Python-C API can be found under
|
|
* 'Extending and Embedding' and 'Python/C API' at
|
|
* docs.python.org .
|
|
*/
|
|
|
|
|
|
/* This declares the logit function */
|
|
static PyObject* spam_logit(PyObject *self, PyObject *args);
|
|
|
|
|
|
/*
|
|
* This tells Python what methods this module has.
|
|
* See the Python-C API for more information.
|
|
*/
|
|
static PyMethodDef SpamMethods[] = {
|
|
{"logit",
|
|
spam_logit,
|
|
METH_VARARGS, "compute logit"},
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
|
|
/*
|
|
* This actually defines the logit function for
|
|
* input args from Python.
|
|
*/
|
|
|
|
static PyObject* spam_logit(PyObject *self, PyObject *args)
|
|
{
|
|
double p;
|
|
|
|
/* This parses the Python argument into a double */
|
|
if(!PyArg_ParseTuple(args, "d", &p)) {
|
|
return NULL;
|
|
}
|
|
|
|
/* THE ACTUAL LOGIT FUNCTION */
|
|
p = p/(1-p);
|
|
p = log(p);
|
|
|
|
/*This builds the answer back into a python object */
|
|
return Py_BuildValue("d", p);
|
|
}
|
|
|
|
|
|
/* This initiates the module using the above definitions. */
|
|
#if PY_VERSION_HEX >= 0x03000000
|
|
static struct PyModuleDef moduledef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"spam",
|
|
NULL,
|
|
-1,
|
|
SpamMethods,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
PyObject *PyInit_spam(void)
|
|
{
|
|
PyObject *m;
|
|
m = PyModule_Create(&moduledef);
|
|
if (!m) {
|
|
return NULL;
|
|
}
|
|
return m;
|
|
}
|
|
#else
|
|
PyMODINIT_FUNC initspam(void)
|
|
{
|
|
PyObject *m;
|
|
|
|
m = Py_InitModule("spam", SpamMethods);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
To use the setup.py file, place setup.py and spammodule.c in the same
|
|
folder. Then python setup.py build will build the module to import,
|
|
or setup.py install will install the module to your site-packages
|
|
directory.
|
|
|
|
.. code-block:: python
|
|
|
|
'''
|
|
setup.py file for spammodule.c
|
|
|
|
Calling
|
|
$python setup.py build_ext --inplace
|
|
will build the extension library in the current file.
|
|
|
|
Calling
|
|
$python setup.py build
|
|
will build a file that looks like ./build/lib*, where
|
|
lib* is a file that begins with lib. The library will
|
|
be in this file and end with a C library extension,
|
|
such as .so
|
|
|
|
Calling
|
|
$python setup.py install
|
|
will install the module in your site-packages file.
|
|
|
|
See the distutils section of
|
|
'Extending and Embedding the Python Interpreter'
|
|
at docs.python.org for more information.
|
|
'''
|
|
|
|
|
|
from distutils.core import setup, Extension
|
|
|
|
module1 = Extension('spam', sources=['spammodule.c'],
|
|
include_dirs=['/usr/local/lib'])
|
|
|
|
setup(name = 'spam',
|
|
version='1.0',
|
|
description='This is my spam package',
|
|
ext_modules = [module1])
|
|
|
|
|
|
Once the spam module is imported into python, you can call logit
|
|
via spam.logit. Note that the function used above cannot be applied
|
|
as-is to numpy arrays. To do so we must call numpy.vectorize on it.
|
|
For example, if a python interpreter is opened in the file containing
|
|
the spam library or spam has been installed, one can perform the
|
|
following commands:
|
|
|
|
>>> import numpy as np
|
|
>>> import spam
|
|
>>> spam.logit(0)
|
|
-inf
|
|
>>> spam.logit(1)
|
|
inf
|
|
>>> spam.logit(0.5)
|
|
0.0
|
|
>>> x = np.linspace(0,1,10)
|
|
>>> spam.logit(x)
|
|
TypeError: only length-1 arrays can be converted to Python scalars
|
|
>>> f = np.vectorize(spam.logit)
|
|
>>> f(x)
|
|
array([ -inf, -2.07944154, -1.25276297, -0.69314718, -0.22314355,
|
|
0.22314355, 0.69314718, 1.25276297, 2.07944154, inf])
|
|
|
|
THE RESULTING LOGIT FUNCTION IS NOT FAST! numpy.vectorize simply
|
|
loops over spam.logit. The loop is done at the C level, but the numpy
|
|
array is constantly being parsed and build back up. This is expensive.
|
|
When the author compared numpy.vectorize(spam.logit) against the
|
|
logit ufuncs constructed below, the logit ufuncs were almost exactly
|
|
4 times faster. Larger or smaller speedups are, of course, possible
|
|
depending on the nature of the function.
|
|
|
|
|
|
.. _`sec:Numpy-one-loop`:
|
|
|
|
Example Numpy ufunc for one dtype
|
|
=================================
|
|
|
|
.. index::
|
|
pair: ufunc; adding new
|
|
|
|
For simplicity we give a ufunc for a single dtype, the 'f8' double.
|
|
As in the previous section, we first give the .c file and then the
|
|
setup.py file used to create the module containing the ufunc.
|
|
|
|
The place in the code corresponding to the actual computations for
|
|
the ufunc are marked with /\*BEGIN main ufunc computation\*/ and
|
|
/\*END main ufunc computation\*/. The code in between those lines is
|
|
the primary thing that must be changed to create your own ufunc.
|
|
|
|
.. code-block:: c
|
|
|
|
#include "Python.h"
|
|
#include "math.h"
|
|
#include "numpy/ndarraytypes.h"
|
|
#include "numpy/ufuncobject.h"
|
|
#include "numpy/npy_3kcompat.h"
|
|
|
|
/*
|
|
* single_type_logit.c
|
|
* This is the C code for creating your own
|
|
* Numpy ufunc for a logit function.
|
|
*
|
|
* In this code we only define the ufunc for
|
|
* a single dtype. The computations that must
|
|
* be replaced to create a ufunc for
|
|
* a different funciton are marked with BEGIN
|
|
* and END.
|
|
*
|
|
* Details explaining the Python-C API can be found under
|
|
* 'Extending and Embedding' and 'Python/C API' at
|
|
* docs.python.org .
|
|
*/
|
|
|
|
static PyMethodDef LogitMethods[] = {
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
/* The loop definition must precede the PyMODINIT_FUNC. */
|
|
|
|
static void double_logit(char **args, npy_intp *dimensions,
|
|
npy_intp* steps, void* data)
|
|
{
|
|
npy_intp i;
|
|
npy_intp n = dimensions[0];
|
|
char *in = args[0], *out = args[1];
|
|
npy_intp in_step = steps[0], out_step = steps[1];
|
|
|
|
double tmp;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
/*BEGIN main ufunc computation*/
|
|
tmp = *(double *)in;
|
|
tmp /= 1-tmp;
|
|
*((double *)out) = log(tmp);
|
|
/*END main ufunc computation*/
|
|
|
|
in += in_step;
|
|
out += out_step;
|
|
}
|
|
}
|
|
|
|
/*This a pointer to the above function*/
|
|
PyUFuncGenericFunction funcs[1] = {&double_logit};
|
|
|
|
/* These are the input and return dtypes of logit.*/
|
|
static char types[2] = {NPY_DOUBLE, NPY_DOUBLE};
|
|
|
|
static void *data[1] = {NULL};
|
|
|
|
#if PY_VERSION_HEX >= 0x03000000
|
|
static struct PyModuleDef moduledef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"npufunc",
|
|
NULL,
|
|
-1,
|
|
LogitMethods,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
PyObject *PyInit_npufunc(void)
|
|
{
|
|
PyObject *m, *logit, *d;
|
|
m = PyModule_Create(&moduledef);
|
|
if (!m) {
|
|
return NULL;
|
|
}
|
|
|
|
import_array();
|
|
import_umath();
|
|
|
|
logit = PyUFunc_FromFuncAndData(funcs, data, types, 1, 1, 1,
|
|
PyUFunc_None, "logit",
|
|
"logit_docstring", 0);
|
|
|
|
d = PyModule_GetDict(m);
|
|
|
|
PyDict_SetItemString(d, "logit", logit);
|
|
Py_DECREF(logit);
|
|
|
|
return m;
|
|
}
|
|
#else
|
|
PyMODINIT_FUNC initnpufunc(void)
|
|
{
|
|
PyObject *m, *logit, *d;
|
|
|
|
|
|
m = Py_InitModule("npufunc", LogitMethods);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
|
|
import_array();
|
|
import_umath();
|
|
|
|
logit = PyUFunc_FromFuncAndData(funcs, data, types, 1, 1, 1,
|
|
PyUFunc_None, "logit",
|
|
"logit_docstring", 0);
|
|
|
|
d = PyModule_GetDict(m);
|
|
|
|
PyDict_SetItemString(d, "logit", logit);
|
|
Py_DECREF(logit);
|
|
}
|
|
#endif
|
|
|
|
This is a setup.py file for the above code. As before, the module
|
|
can be build via calling python setup.py build at the command prompt,
|
|
or installed to site-packages via python setup.py install.
|
|
|
|
.. code-block:: python
|
|
|
|
'''
|
|
setup.py file for logit.c
|
|
Note that since this is a numpy extension
|
|
we use numpy.distutils instead of
|
|
distutils from the python standard library.
|
|
|
|
Calling
|
|
$python setup.py build_ext --inplace
|
|
will build the extension library in the current file.
|
|
|
|
Calling
|
|
$python setup.py build
|
|
will build a file that looks like ./build/lib*, where
|
|
lib* is a file that begins with lib. The library will
|
|
be in this file and end with a C library extension,
|
|
such as .so
|
|
|
|
Calling
|
|
$python setup.py install
|
|
will install the module in your site-packages file.
|
|
|
|
See the distutils section of
|
|
'Extending and Embedding the Python Interpreter'
|
|
at docs.python.org and the documentation
|
|
on numpy.distutils for more information.
|
|
'''
|
|
|
|
|
|
def configuration(parent_package='', top_path=None):
|
|
import numpy
|
|
from numpy.distutils.misc_util import Configuration
|
|
|
|
config = Configuration('npufunc_directory',
|
|
parent_package,
|
|
top_path)
|
|
config.add_extension('npufunc', ['single_type_logit.c'])
|
|
|
|
return config
|
|
|
|
if __name__ == "__main__":
|
|
from numpy.distutils.core import setup
|
|
setup(configuration=configuration)
|
|
|
|
After the above has been installed, it can be imported and used as follows.
|
|
|
|
>>> import numpy as np
|
|
>>> import npufunc
|
|
>>> npufunc.logit(0.5)
|
|
0.0
|
|
>>> a = np.linspace(0,1,5)
|
|
>>> npufunc.logit(a)
|
|
array([ -inf, -1.09861229, 0. , 1.09861229, inf])
|
|
|
|
|
|
|
|
.. _`sec:Numpy-many-loop`:
|
|
|
|
Example Numpy ufunc with multiple dtypes
|
|
========================================
|
|
|
|
.. index::
|
|
pair: ufunc; adding new
|
|
|
|
We finally give an example of a full ufunc, with inner loops for
|
|
half-floats, floats, doubles, and long doubles. As in the previous
|
|
sections we first give the .c file and then the corresponding
|
|
setup.py file.
|
|
|
|
The places in the code corresponding to the actual computations for
|
|
the ufunc are marked with /\*BEGIN main ufunc computation\*/ and
|
|
/\*END main ufunc computation\*/. The code in between those lines is
|
|
the primary thing that must be changed to create your own ufunc.
|
|
|
|
|
|
.. code-block:: c
|
|
|
|
#include "Python.h"
|
|
#include "math.h"
|
|
#include "numpy/ndarraytypes.h"
|
|
#include "numpy/ufuncobject.h"
|
|
#include "numpy/halffloat.h"
|
|
|
|
/*
|
|
* multi_type_logit.c
|
|
* This is the C code for creating your own
|
|
* Numpy ufunc for a logit function.
|
|
*
|
|
* Each function of the form type_logit defines the
|
|
* logit function for a different numpy dtype. Each
|
|
* of these functions must be modified when you
|
|
* create your own ufunc. The computations that must
|
|
* be replaced to create a ufunc for
|
|
* a different funciton are marked with BEGIN
|
|
* and END.
|
|
*
|
|
* Details explaining the Python-C API can be found under
|
|
* 'Extending and Embedding' and 'Python/C API' at
|
|
* docs.python.org .
|
|
*
|
|
*/
|
|
|
|
|
|
static PyMethodDef LogitMethods[] = {
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
/* The loop definitions must precede the PyMODINIT_FUNC. */
|
|
|
|
static void long_double_logit(char **args, npy_intp *dimensions,
|
|
npy_intp* steps, void* data)
|
|
{
|
|
npy_intp i;
|
|
npy_intp n = dimensions[0];
|
|
char *in = args[0], *out=args[1];
|
|
npy_intp in_step = steps[0], out_step = steps[1];
|
|
|
|
long double tmp;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
/*BEGIN main ufunc computation*/
|
|
tmp = *(long double *)in;
|
|
tmp /= 1-tmp;
|
|
*((long double *)out) = logl(tmp);
|
|
/*END main ufunc computation*/
|
|
|
|
in += in_step;
|
|
out += out_step;
|
|
}
|
|
}
|
|
|
|
static void double_logit(char **args, npy_intp *dimensions,
|
|
npy_intp* steps, void* data)
|
|
{
|
|
npy_intp i;
|
|
npy_intp n = dimensions[0];
|
|
char *in = args[0], *out = args[1];
|
|
npy_intp in_step = steps[0], out_step = steps[1];
|
|
|
|
double tmp;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
/*BEGIN main ufunc computation*/
|
|
tmp = *(double *)in;
|
|
tmp /= 1-tmp;
|
|
*((double *)out) = log(tmp);
|
|
/*END main ufunc computation*/
|
|
|
|
in += in_step;
|
|
out += out_step;
|
|
}
|
|
}
|
|
|
|
static void float_logit(char **args, npy_intp *dimensions,
|
|
npy_intp* steps, void* data)
|
|
{
|
|
npy_intp i;
|
|
npy_intp n = dimensions[0];
|
|
char *in=args[0], *out = args[1];
|
|
npy_intp in_step = steps[0], out_step = steps[1];
|
|
|
|
float tmp;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
/*BEGIN main ufunc computation*/
|
|
tmp = *(float *)in;
|
|
tmp /= 1-tmp;
|
|
*((float *)out) = logf(tmp);
|
|
/*END main ufunc computation*/
|
|
|
|
in += in_step;
|
|
out += out_step;
|
|
}
|
|
}
|
|
|
|
|
|
static void half_float_logit(char **args, npy_intp *dimensions,
|
|
npy_intp* steps, void* data)
|
|
{
|
|
npy_intp i;
|
|
npy_intp n = dimensions[0];
|
|
char *in = args[0], *out = args[1];
|
|
npy_intp in_step = steps[0], out_step = steps[1];
|
|
|
|
float tmp;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
/*BEGIN main ufunc computation*/
|
|
tmp = *(npy_half *)in;
|
|
tmp = npy_half_to_float(tmp);
|
|
tmp /= 1-tmp;
|
|
tmp = logf(tmp);
|
|
*((npy_half *)out) = npy_float_to_half(tmp);
|
|
/*END main ufunc computation*/
|
|
|
|
in += in_step;
|
|
out += out_step;
|
|
}
|
|
}
|
|
|
|
|
|
/*This gives pointers to the above functions*/
|
|
PyUFuncGenericFunction funcs[4] = {&half_float_logit,
|
|
&float_logit,
|
|
&double_logit,
|
|
&long_double_logit};
|
|
|
|
static char types[8] = {NPY_HALF, NPY_HALF,
|
|
NPY_FLOAT, NPY_FLOAT,
|
|
NPY_DOUBLE,NPY_DOUBLE,
|
|
NPY_LONGDOUBLE, NPY_LONGDOUBLE};
|
|
static void *data[4] = {NULL, NULL, NULL, NULL};
|
|
|
|
#if PY_VERSION_HEX >= 0x03000000
|
|
static struct PyModuleDef moduledef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"npufunc",
|
|
NULL,
|
|
-1,
|
|
LogitMethods,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
PyObject *PyInit_npufunc(void)
|
|
{
|
|
PyObject *m, *logit, *d;
|
|
m = PyModule_Create(&moduledef);
|
|
if (!m) {
|
|
return NULL;
|
|
}
|
|
|
|
import_array();
|
|
import_umath();
|
|
|
|
logit = PyUFunc_FromFuncAndData(funcs, data, types, 4, 1, 1,
|
|
PyUFunc_None, "logit",
|
|
"logit_docstring", 0);
|
|
|
|
d = PyModule_GetDict(m);
|
|
|
|
PyDict_SetItemString(d, "logit", logit);
|
|
Py_DECREF(logit);
|
|
|
|
return m;
|
|
}
|
|
#else
|
|
PyMODINIT_FUNC initnpufunc(void)
|
|
{
|
|
PyObject *m, *logit, *d;
|
|
|
|
|
|
m = Py_InitModule("npufunc", LogitMethods);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
|
|
import_array();
|
|
import_umath();
|
|
|
|
logit = PyUFunc_FromFuncAndData(funcs, data, types, 4, 1, 1,
|
|
PyUFunc_None, "logit",
|
|
"logit_docstring", 0);
|
|
|
|
d = PyModule_GetDict(m);
|
|
|
|
PyDict_SetItemString(d, "logit", logit);
|
|
Py_DECREF(logit);
|
|
}
|
|
#endif
|
|
|
|
This is a setup.py file for the above code. As before, the module
|
|
can be build via calling python setup.py build at the command prompt,
|
|
or installed to site-packages via python setup.py install.
|
|
|
|
.. code-block:: python
|
|
|
|
'''
|
|
setup.py file for logit.c
|
|
Note that since this is a numpy extension
|
|
we use numpy.distutils instead of
|
|
distutils from the python standard library.
|
|
|
|
Calling
|
|
$python setup.py build_ext --inplace
|
|
will build the extension library in the current file.
|
|
|
|
Calling
|
|
$python setup.py build
|
|
will build a file that looks like ./build/lib*, where
|
|
lib* is a file that begins with lib. The library will
|
|
be in this file and end with a C library extension,
|
|
such as .so
|
|
|
|
Calling
|
|
$python setup.py install
|
|
will install the module in your site-packages file.
|
|
|
|
See the distutils section of
|
|
'Extending and Embedding the Python Interpreter'
|
|
at docs.python.org and the documentation
|
|
on numpy.distutils for more information.
|
|
'''
|
|
|
|
|
|
def configuration(parent_package='', top_path=None):
|
|
import numpy
|
|
from numpy.distutils.misc_util import Configuration
|
|
from numpy.distutils.misc_util import get_info
|
|
|
|
#Necessary for the half-float d-type.
|
|
info = get_info('npymath')
|
|
|
|
config = Configuration('npufunc_directory',
|
|
parent_package,
|
|
top_path)
|
|
config.add_extension('npufunc',
|
|
['multi_type_logit.c'],
|
|
extra_info=info)
|
|
|
|
return config
|
|
|
|
if __name__ == "__main__":
|
|
from numpy.distutils.core import setup
|
|
setup(configuration=configuration)
|
|
|
|
After the above has been installed, it can be imported and used as follows.
|
|
|
|
>>> import numpy as np
|
|
>>> import npufunc
|
|
>>> npufunc.logit(0.5)
|
|
0.0
|
|
>>> a = np.linspace(0,1,5)
|
|
>>> npufunc.logit(a)
|
|
array([ -inf, -1.09861229, 0. , 1.09861229, inf])
|
|
|
|
|
|
|
|
.. _`sec:Numpy-many-arg`:
|
|
|
|
Example Numpy ufunc with multiple arguments/return values
|
|
=========================================================
|
|
|
|
Our final example is a ufunc with multiple arguments. It is a modification
|
|
of the code for a logit ufunc for data with a single dtype. We
|
|
compute (A*B, logit(A*B)).
|
|
|
|
We only give the C code as the setup.py file is exactly the same as
|
|
the setup.py file in `Example Numpy ufunc for one dtype`_, except that
|
|
the line
|
|
|
|
.. code-block:: python
|
|
|
|
config.add_extension('npufunc', ['single_type_logit.c'])
|
|
|
|
is replaced with
|
|
|
|
.. code-block:: python
|
|
|
|
config.add_extension('npufunc', ['multi_arg_logit.c'])
|
|
|
|
The C file is given below. The ufunc generated takes two arguments A
|
|
and B. It returns a tuple whose first element is A*B and whose second
|
|
element is logit(A*B). Note that it automatically supports broadcasting,
|
|
as well as all other properties of a ufunc.
|
|
|
|
.. code-block:: c
|
|
|
|
#include "Python.h"
|
|
#include "math.h"
|
|
#include "numpy/ndarraytypes.h"
|
|
#include "numpy/ufuncobject.h"
|
|
#include "numpy/halffloat.h"
|
|
|
|
/*
|
|
* multi_arg_logit.c
|
|
* This is the C code for creating your own
|
|
* Numpy ufunc for a multiple argument, multiple
|
|
* return value ufunc. The places where the
|
|
* ufunc computation is carried out are marked
|
|
* with comments.
|
|
*
|
|
* Details explaining the Python-C API can be found under
|
|
* 'Extending and Embedding' and 'Python/C API' at
|
|
* docs.python.org .
|
|
*
|
|
*/
|
|
|
|
|
|
static PyMethodDef LogitMethods[] = {
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
/* The loop definition must precede the PyMODINIT_FUNC. */
|
|
|
|
static void double_logitprod(char **args, npy_intp *dimensions,
|
|
npy_intp* steps, void* data)
|
|
{
|
|
npy_intp i;
|
|
npy_intp n = dimensions[0];
|
|
char *in1 = args[0], *in2 = args[1];
|
|
char *out1 = args[2], *out2 = args[3];
|
|
npy_intp in1_step = steps[0], in2_step = steps[1];
|
|
npy_intp out1_step = steps[2], out2_step = steps[3];
|
|
|
|
double tmp;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
/*BEGIN main ufunc computation*/
|
|
tmp = *(double *)in1;
|
|
tmp *= *(double *)in2;
|
|
*((double *)out1) = tmp;
|
|
*((double *)out2) = log(tmp/(1-tmp));
|
|
/*END main ufunc computation*/
|
|
|
|
in1 += in1_step;
|
|
in2 += in2_step;
|
|
out1 += out1_step;
|
|
out2 += out2_step;
|
|
}
|
|
}
|
|
|
|
|
|
/*This a pointer to the above function*/
|
|
PyUFuncGenericFunction funcs[1] = {&double_logitprod};
|
|
|
|
/* These are the input and return dtypes of logit.*/
|
|
|
|
static char types[4] = {NPY_DOUBLE, NPY_DOUBLE,
|
|
NPY_DOUBLE, NPY_DOUBLE};
|
|
|
|
|
|
static void *data[1] = {NULL};
|
|
|
|
#if PY_VERSION_HEX >= 0x03000000
|
|
static struct PyModuleDef moduledef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"npufunc",
|
|
NULL,
|
|
-1,
|
|
LogitMethods,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
PyObject *PyInit_npufunc(void)
|
|
{
|
|
PyObject *m, *logit, *d;
|
|
m = PyModule_Create(&moduledef);
|
|
if (!m) {
|
|
return NULL;
|
|
}
|
|
|
|
import_array();
|
|
import_umath();
|
|
|
|
logit = PyUFunc_FromFuncAndData(funcs, data, types, 1, 2, 2,
|
|
PyUFunc_None, "logit",
|
|
"logit_docstring", 0);
|
|
|
|
d = PyModule_GetDict(m);
|
|
|
|
PyDict_SetItemString(d, "logit", logit);
|
|
Py_DECREF(logit);
|
|
|
|
return m;
|
|
}
|
|
#else
|
|
PyMODINIT_FUNC initnpufunc(void)
|
|
{
|
|
PyObject *m, *logit, *d;
|
|
|
|
|
|
m = Py_InitModule("npufunc", LogitMethods);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
|
|
import_array();
|
|
import_umath();
|
|
|
|
logit = PyUFunc_FromFuncAndData(funcs, data, types, 1, 2, 2,
|
|
PyUFunc_None, "logit",
|
|
"logit_docstring", 0);
|
|
|
|
d = PyModule_GetDict(m);
|
|
|
|
PyDict_SetItemString(d, "logit", logit);
|
|
Py_DECREF(logit);
|
|
}
|
|
#endif
|
|
|
|
.. _`sec:PyUFunc-spec`:
|
|
|
|
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.
|
|
|
|
|
|
.. cfunction:: PyObject *PyUFunc_FromFuncAndData( PyUFuncGenericFunction* func,
|
|
void** data, char* types, int ntypes, int nin, int nout, int identity,
|
|
char* name, char* doc, int check_return)
|
|
|
|
*func*
|
|
|
|
A pointer to an array of 1-d functions to use. This array must be at
|
|
least ntypes long. Each entry in the array must be a
|
|
``PyUFuncGenericFunction`` function. This function has the following
|
|
signature. An example of a valid 1d loop function is also given.
|
|
|
|
.. cfunction:: void loop1d(char** args, npy_intp* dimensions,
|
|
npy_intp* steps, void* data)
|
|
|
|
*args*
|
|
|
|
An array of pointers to the actual data for the input and output
|
|
arrays. The input arguments are given first followed by the output
|
|
arguments.
|
|
|
|
*dimensions*
|
|
|
|
A pointer to the size of the dimension over which this function is
|
|
looping.
|
|
|
|
*steps*
|
|
|
|
A pointer to the number of bytes to jump to get to the
|
|
next element in this dimension for each of the input and
|
|
output arguments.
|
|
|
|
*data*
|
|
|
|
Arbitrary data (extra arguments, function names, *etc.* )
|
|
that can be stored with the ufunc and will be passed in
|
|
when it is called.
|
|
|
|
.. code-block:: c
|
|
|
|
static void
|
|
double_add(char *args, npy_intp *dimensions, npy_intp *steps,
|
|
void *extra)
|
|
{
|
|
npy_intp i;
|
|
npy_intp is1 = steps[0], is2 = steps[1];
|
|
npy_intp os = steps[2], n = dimensions[0];
|
|
char *i1 = args[0], *i2 = args[1], *op = args[2];
|
|
for (i = 0; i < n; i++) {
|
|
*((double *)op) = *((double *)i1) +
|
|
*((double *)i2);
|
|
i1 += is1;
|
|
i2 += is2;
|
|
op += os;
|
|
}
|
|
}
|
|
|
|
*data*
|
|
|
|
An array of data. There should be ntypes entries (or NULL) --- one for
|
|
every loop function defined for this ufunc. This data will be passed
|
|
in to the 1-d loop. One common use of this data variable is to pass in
|
|
an actual function to call to compute the result when a generic 1-d
|
|
loop (e.g. :cfunc:`PyUFunc_d_d`) is being used.
|
|
|
|
*types*
|
|
|
|
An array of type-number signatures (type ``char`` ). This
|
|
array should be of size (nin+nout)*ntypes and contain the
|
|
data-types for the corresponding 1-d loop. The inputs should
|
|
be first followed by the outputs. For example, suppose I have
|
|
a ufunc that supports 1 integer and 1 double 1-d loop
|
|
(length-2 func and data arrays) that takes 2 inputs and
|
|
returns 1 output that is always a complex double, then the
|
|
types array would be
|
|
|
|
.. code-block:: c
|
|
|
|
static char types[3] = {NPY_INT, NPY_DOUBLE, NPY_CDOUBLE}
|
|
|
|
The bit-width names can also be used (e.g. :cdata:`NPY_INT32`,
|
|
:cdata:`NPY_COMPLEX128` ) if desired.
|
|
|
|
*ntypes*
|
|
|
|
The number of data-types supported. This is equal to the number of 1-d
|
|
loops provided.
|
|
|
|
*nin*
|
|
|
|
The number of input arguments.
|
|
|
|
*nout*
|
|
|
|
The number of output arguments.
|
|
|
|
*identity*
|
|
|
|
Either :cdata:`PyUFunc_One`, :cdata:`PyUFunc_Zero`,
|
|
:cdata:`PyUFunc_None`. This specifies what should be returned when
|
|
an empty array is passed to the reduce method of the ufunc.
|
|
|
|
*name*
|
|
|
|
A ``NULL`` -terminated string providing the name of this ufunc
|
|
(should be the Python name it will be called).
|
|
|
|
*doc*
|
|
|
|
A documentation string for this ufunc (will be used in generating the
|
|
response to ``{ufunc_name}.__doc__``). Do not include the function
|
|
signature or the name as this is generated automatically.
|
|
|
|
*check_return*
|
|
|
|
Not presently used, but this integer value does get set in the
|
|
structure-member of similar name.
|
|
|
|
.. index::
|
|
pair: ufunc; adding new
|
|
|
|
The returned ufunc object is a callable Python object. It should be
|
|
placed in a (module) dictionary under the same name as was used in the
|
|
name argument to the ufunc-creation routine. The following example is
|
|
adapted from the umath module
|
|
|
|
.. code-block:: c
|
|
|
|
static PyUFuncGenericFunction atan2_functions[] = {
|
|
PyUFunc_ff_f, PyUFunc_dd_d,
|
|
PyUFunc_gg_g, PyUFunc_OO_O_method};
|
|
static void* atan2_data[] = {
|
|
(void *)atan2f,(void *) atan2,
|
|
(void *)atan2l,(void *)"arctan2"};
|
|
static char atan2_signatures[] = {
|
|
NPY_FLOAT, NPY_FLOAT, NPY_FLOAT,
|
|
NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE,
|
|
NPY_LONGDOUBLE, NPY_LONGDOUBLE, NPY_LONGDOUBLE
|
|
NPY_OBJECT, NPY_OBJECT, NPY_OBJECT};
|
|
...
|
|
/* in the module initialization code */
|
|
PyObject *f, *dict, *module;
|
|
...
|
|
dict = PyModule_GetDict(module);
|
|
...
|
|
f = PyUFunc_FromFuncAndData(atan2_functions,
|
|
atan2_data, atan2_signatures, 4, 2, 1,
|
|
PyUFunc_None, "arctan2",
|
|
"a safe and correct arctan(x1/x2)", 0);
|
|
PyDict_SetItemString(dict, "arctan2", f);
|
|
Py_DECREF(f);
|
|
...
|