Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 28ead01

Browse files
initial revision of ufuncs document
1 parent 16767fc commit 28ead01

1 file changed

Lines changed: 203 additions & 0 deletions

File tree

doc/ufuncs-draft.txt

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
Writing Your Own UFunc and Generalized UFunc For a Custom Data Type
2+
3+
The following are several examples for creating your own ufuncs and generalized ufuncs for custom data types.
4+
All the examples use the custom dtype 'Rational' located in the numpy-dtypes repository on github.com
5+
6+
7+
Extending Existing UFunc for Custom DType
8+
9+
The first example shows how to extend the existing 'add' ufunc for the Rational dtype. The add ufunc is extended
10+
for Rational dtypes using Rational's 'rational_add' function which takes two rational numbers and returns a rational
11+
object representing the sum of those two rational numbers:
12+
13+
static NPY_INLINE rational
14+
rational_add(rational x, rational y) {
15+
return make_rational_fast((int64_t)x.n*d(y) + (int64_t)d(x)*y.n, (int64_t)d(x)*d(y));
16+
}
17+
18+
1. A 1-d loop function is created which loops over each pair of elements from two 1-d arrays of rationals and
19+
calls rational_add for each pair:
20+
21+
void rational_ufunc_add(char** args, npy_intp* dimensions, npy_intp* steps, void* data) {
22+
npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions;
23+
char *i0 = args[0], *i1 = args[1], *o = args[2];
24+
int k;
25+
for (k = 0; k < n; k++) {
26+
rational x = *(rational*)i0;
27+
rational y = *(rational*)i1;
28+
*(rational*)o = rational_add(x,y);
29+
i0 += is0; i1 += is1; o += os;
30+
}
31+
}
32+
33+
The loop function must have the exact signature as above. The function parameters are:
34+
35+
char **args - array of pointers to the actual data for the input and output arrays. In this example there are
36+
three pointers: two pointers pointing to blocks of memory for the two input arrays, and one pointer pointing to
37+
the block of memory for the output array. The result of rational_add should be stored in the output array.
38+
39+
dimensions - a pointer to the size of the dimension over which this function is looping
40+
41+
steps - a pointer to the number of bytes to jump to get to the next element in this dimension
42+
for each of the input and output arguments
43+
44+
data - arbitrary data (extra arguments, function names, etc.) that can be stored with the ufunc
45+
and will be passed in when it is called
46+
47+
The Rational dtype has an example of a C macro which can be used to generate create the above function for
48+
different rational ufuncs:
49+
50+
#define BINARY_UFUNC(name,intype0,intype1,outtype,exp) \
51+
void name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \
52+
npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions; \
53+
char *i0 = args[0], *i1 = args[1], *o = args[2]; \
54+
int k; \
55+
for (k = 0; k < n; k++) { \
56+
intype0 x = *(intype0*)i0; \
57+
intype1 y = *(intype1*)i1; \
58+
*(outtype*)o = exp; \
59+
i0 += is0; i1 += is1; o += os; \
60+
} \
61+
}
62+
63+
#define RATIONAL_BINARY_UFUNC(name, type, exp) BINARY_UFUNC(rational_ufunc_##name, rational, rational, type, exp)
64+
65+
which can be used like so:
66+
67+
RATIONAL_BINARY_UFUNC(add, rational, rational_add(x,y))
68+
69+
with the following arguments:
70+
- name suffix of 1-d loop function (the generated loop function will have the name rational_ufunc_<name>'
71+
- output type
72+
- expression to calculate the output value for each pair of input elements. In this example the expression
73+
is a call to the function rational_add.
74+
75+
76+
2. In the 'initrational' function used to initialize the Rational dtype with numpy, a PyUFuncObject is obtained for
77+
the existing 'add' ufunc in the numpy module:
78+
79+
PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,"add");
80+
81+
82+
3. The 1-d loop function is registered using the PyUFuncObject obtained in step 2:
83+
84+
int types[] = {npy_rational,npy_rational,npy_rational};
85+
86+
if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_biggest,types,0) < 0) {
87+
return;
88+
}
89+
90+
The function parameters are:
91+
- pointer to PyUFuncObject obtained in step 2
92+
- custom rational dtype id (obtained when dtype is registered with call to PyArray_RegisterDataType)
93+
- 1-d loop function
94+
- array of input and output type ids (in this case two input rational types and one output rational type)
95+
- pointer to arbitrary data that will be passed to 1-d loop function
96+
97+
98+
4. Steps 2-3 can also be accomplished by using a c MACRO similar to the one provided with Rational:
99+
100+
#define REGISTER_UFUNC(name,...) { \
101+
PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,#name); \
102+
if (!ufunc) { \
103+
return; \
104+
} \
105+
int _types[] = __VA_ARGS__; \
106+
if (sizeof(_types)/sizeof(int)!=ufunc->nargs) { \
107+
PyErr_Format(PyExc_AssertionError,"ufunc %s takes %d arguments, our loop takes %ld",#name,ufunc->nargs,sizeof(_types)/sizeof(int)); \
108+
return; \
109+
} \
110+
if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,_types,0)<0) { \
111+
return; \
112+
} \
113+
}
114+
#define REGISTER_UFUNC_BINARY_RATIONAL(name) REGISTER_UFUNC(name,{npy_rational,npy_rational,npy_rational})
115+
116+
REGISTER_UFUNC_BINARY_RATIONAL(add)
117+
118+
119+
120+
Creating New UFunc for Custom DType
121+
122+
The next example shows how to create a new ufunc for the Rational dtype. The ufunc example is called 'numerator'
123+
and generates an array of numerator values based rational numbers from the input array.
124+
125+
126+
1. A 1-d loop function is created as before which takes the numerator value from each element of the input array
127+
and stores it in the output array:
128+
129+
void rational_ufunc_numerator(char** args, npy_intp* dimensions, npy_intp* steps, void* data) {
130+
npy_intp is = steps[0], os = steps[1], n = *dimensions;
131+
char *i = args[0], *o = args[1];
132+
int k;
133+
for (k = 0; k < n; k++) {
134+
rational x = *(rational*)i;
135+
*(int64_t*)o = x.n;
136+
i += is; o += os;
137+
}
138+
}
139+
140+
You can also use the c MACRO provided in Rational for generating the above function:
141+
142+
#define UNARY_UFUNC(name,type,exp) \
143+
void rational_ufunc_##name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \
144+
npy_intp is = steps[0], os = steps[1], n = *dimensions; \
145+
char *i = args[0], *o = args[1]; \
146+
int k; \
147+
for (k = 0; k < n; k++) { \
148+
rational x = *(rational*)i; \
149+
*(type*)o = exp; \
150+
i += is; o += os; \
151+
} \
152+
}
153+
154+
UNARY_UFUNC(numerator,int64_t,x.n)
155+
156+
157+
2. In the 'initrational' function used to initialize the Rational dtype with numpy, a new PyUFuncObject is created
158+
for the new 'numerator' ufunc using the PyUFunc_FromFuncAndData function:
159+
160+
PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)"numerator",(char*)"rational number numerator",0);
161+
162+
In this use case, the first four parameters should be set to zero since we're creating a new ufunc instead of
163+
extending an existing one. The rest of the parameters:
164+
165+
- number of inputs to function that the loop function calls for each pair of elements
166+
- number of outputs of loop function
167+
- name of the ufunc
168+
- documentation string describing the ufunc
169+
- unused; present for backwards compatibility
170+
171+
172+
3. The 1-d loop function is registered using the loop function and the PyUFuncObject created in step 2:
173+
174+
int _types[] = {npy_rational,NPY_INT64};
175+
176+
if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_numerator,_types,0)<0) {
177+
return;
178+
}
179+
180+
181+
4. Finally, a function called 'numerator' is added to the rational module which will call the numerator ufunc:
182+
183+
PyModule_AddObject(m,"numerator",(PyObject*)ufunc);
184+
185+
186+
5. Steps 2-4 can also be accomplished by using a c MACRO similar to the one provided with Rational:
187+
188+
#define NEW_UNARY_UFUNC(name,type,doc) { \
189+
PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)#name,(char*)doc,0); \
190+
if (!ufunc) { \
191+
return; \
192+
} \
193+
int types[2] = {npy_rational,type}; \
194+
if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,types,0)<0) { \
195+
return; \
196+
} \
197+
PyModule_AddObject(m,#name,(PyObject*)ufunc); \
198+
}
199+
200+
NEW_UNARY_UFUNC(numerator,NPY_INT64,"rational number numerator");
201+
202+
203+

0 commit comments

Comments
 (0)