The element-wise in-place API is a family of macros and functions designed to get and set elements of arrays which might be byteswapped, misaligned, discontiguous, or of a different type. You can obtain PyArrayObjects for these misbehaved arrays from the high-level API by specifying fewer requirements (perhaps just 0, rather than NUM_C_ARRAY). In this way, you can avoid the creation of temporaries at a cost of accessing your array with these macros and functions and a significant performance penalty. Make no mistake, if you have the memory, the high level API is the fastest. The whole point of this API is to support cases where the creation of temporaries exhausts either the physical or virtual address space. Exhausting physical memory will result in thrashing, while exhausting the virtual address space will result in program exception and failure. This API supports avoiding the creation of the temporaries, and thus avoids exhausting physical and virual memory, possibly improving net performance or even enabling program success where simpler methods would just fail.
The single element macros each access one element of an array at a time, and specify the array type in two places: as part of the PyArrayObject type descriptor, and as ``type''. The former defines what the array is, and the latter is required to produce correct code from the macro. They should match. When you pass ``type'' into one of these macros, you are defining the kind of array the code can operate on. It is an error to pass a non-matching array to one of these macros. One last piece of advice: call these macros carefully, because the resulting expansions and error messages are a *obscene*. Note: the type parameter for a macro is one of the Numarray Numeric Data Types, not a NumarrayType enumeration value.
The Int64/Float64/Complex64 functions require a function call to access a single element of an array, making them slower than the single element macros. They have two advantages:
While these functions have no error return status, they *can* alter the Python error state, so well written extensions should call PyErr_Occurred() to determine if an error occurred and report it. It's reasonable to do this check once at the end of an extension function, rather than on a per-element basis.
The convolve1D wrapper function corresponding to section 10.3.3 using the element-wise API could look like:10.2
static PyObject *
Py_Convolve1d(PyObject *obj, PyObject *args)
{
PyObject *okernel, *odata, *oconvolved=Py_None;
PyArrayObject *kernel, *data, *convolved;
if (!PyArg_ParseTuple(args, "OO|O", &okernel, &odata, &oconvolved))
return PyErr_Format(_Error,
"Convolve1d: Invalid parameters.");
kernel = NA_InputArray(okernel, tAny, 0);
data = NA_InputArray(odata, tAny, 0);
For the kernel and data arrays, numarrays of any type are accepted without conversion. Thus there is no copy of the data made except for lists or tuples. All types, byteswapping, misalignment, and discontiguity must be handled by Convolve1d. This can be done easily using the get/set functions. Macros, while faster than the functions, can only handle a single type.
convolved = NA_OptionalOutputArray(oconvolved, tFloat64, 0, data);
Also for the output array we accept any variety of type tFloat without conversion. No copy is made except for non-tFloat. Non-numarray sequences are not permitted as output arrays. Byteswaping, misaligment, and discontiguity must be handled by Convolve1d. If the Pythoncaller did not specify the oconvolved array, it initially retains the value Py_None. In that case, convolved is cloned from the array data using the specified type. It is important to clone from data and not odata, since the latter may be an ordinary Pythonsequence which was converted into numarray data.
if (!kernel || !data || !convolved)
return NULL;
if ((kernel->nd != 1) || (data->nd != 1))
return PyErr_Format(_Error,
"Convolve1d: arrays must have exactly 1
dimension.");
if (!NA_ShapeEqual(data, convolved))
return PyErr_Format(_Error,
"Convolve1d: data and output arrays must have identical length.");
if (!NA_ShapeLessThan(kernel, data))
return PyErr_Format(_Error,
"Convolve1d: kernel must be smaller than data in both dimensions");
if (Convolve1d(kernel, data, convolved) < 0) /* Error? */
return NULL;
else {
Py_XDECREF(kernel);
Py_XDECREF(data);
return NA_ReturnOutput(oconvolved, convolved);
}
}
This function is very similar to the high-level API wrapper, the notable difference is that we ask for the unconverted arrays kernel and data and convolved. This requires some attention in their usage. The function that does the actual convolution in the example has to use NA_get* to read and NA_set* to set an element of these arrays, instead of using straight array notation. These functions perform any necessary type conversion, byteswapping, and alignment.
static int
Convolve1d(PyArrayObject *kernel, PyArrayObject *data, PyArrayObject *convolved)
{
int xc, xk;
int ksizex = kernel->dimensions[0];
int halfk = ksizex / 2;
int dsizex = data->dimensions[0];
for(xc=0; xc<halfk; xc++)
NA_set1_Float64(convolved, xc, NA_get1_Float64(data, xc));
for(xc=dsizex-halfk; xc<dsizex; xc++)
NA_set1_Float64(convolved, xc, NA_get1_Float64(data, xc));
for(xc=halfk; xc<dsizex-halfk; xc++) {
Float64 temp = 0;
for (xk=0; xk<ksizex; xk++) {
int i = xc - halfk + xk;
temp += NA_get1_Float64(kernel, xk) *
NA_get1_Float64(data, i);
}
NA_set1_Float64(convolved, xc, temp);
}
if (PyErr_Occurred())
return -1;
else
return 0;
}