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.
14.5.1 Element-wise functions
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:
They're function calls, so they're a little more robust.
They can handle any input array type and behavior properties.
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.
void NA_get_offset(
PyArrayObject *, int N, ...)
NA_get_offset computes the offset into an array object given a
variable number of indices. It is not especially robust, and it is
considered an error to pass it more indices than the array has, or indices
which are negative or out of range.
Float64 NA_get_Float64(
PyArrayObject *, long offset)
void NA_set_Float64(
PyArrayObject *, long offset, Float64 v)
Float64 NA_get1_Float64(
PyArrayObject *, int i)
void NA_set1_Float64(
PyArrayObject *, int i, Float64 v)
Float64 NA_get2_Float64(
PyArrayObject *, int i, int j)
void NA_set2_Float64(
PyArrayObject *, int i, int j, Float64 v)
Int64 NA_get_Int64(
PyArrayObject *, long offset)
void NA_set_Int64(
PyArrayObject *, long offset, Int64 v)
Int64 NA_get1_Int64(
PyArrayObject *, int i)
void NA_set1_Int64(
PyArrayObject *, int i, Int64 v)
Int64 NA_get2_Int64(
PyArrayObject *, int i, int j)
void NA_set2_Int64(
PyArrayObject *, int i, int j, Int64 v)
Complex64 NA_get_Complex64(
PyArrayObject *, long offset)
void NA_set_Complex64(
PyArrayObject *, long offset, Complex64 v)
Complex64 NA_get1_Complex64(
PyArrayObject *, int i)
void NA_set1_Complex64(
PyArrayObject *, int i, Complex64 v)
Complex64 NA_get2_Complex64(
PyArrayObject *, int i, int j)
void NA_set2_Complex64(
PyArrayObject *, int i, int j, Complex64 v)
14.5.2 Example
The convolve1D wrapper function corresponding to section
14.4.3 using the element-wise API could look
like:14.2
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.
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)
goto _fail;
if ((kernel->nd != 1) || (data->nd != 1)) {
PyErr_Format(_Error,
"Convolve1d: arrays must have exactly 1 dimension.");
goto _fail;
}
if (!NA_ShapeEqual(data, convolved)) {
PyErr_Format(_Error,
"Convolve1d: data and output arrays must have identical length.");
goto _fail;
}
if (!NA_ShapeLessThan(kernel, data)) {
PyErr_Format(_Error,
"Convolve1d: kernel must be smaller than data in both dimensions");
goto _fail;
}
if (Convolve1d(kernel, data, convolved) < 0) /* Error? */
goto _fail;
else {
Py_XDECREF(kernel);
Py_XDECREF(data);
return NA_ReturnOutput(oconvolved, convolved);
}
_fail:
Py_XDECREF(kernel);
Py_XDECREF(data);
Py_XDECREF(convolved);
return NULL;
}
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;
}