IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
14.5 Element-wise API


14.5 Element-wise API

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.


14.5.1.1 Pointer based single element macros

 NA_GETPa(PyArrayObject*, type, char*)
aligning
 NA_GETPb(PyArrayObject*, type, char*)
byteswapping
 NA_GETPf(PyArrayObject*, type, char*)
fast (well-behaved)
 NA_GETP(PyArrayObject*, type, char*)
testing: any of above
 NA_SETPa(PyArrayObject*, type, char*, v)
 NA_SETPb(PyArrayObject*, type, char*, v)
 NA_SETPf(PyArrayObject*, type, char*, v)
 NA_SETP(PyArrayObject*, type, char*, v)

14.5.1.2 One index single element macros

 NA_GET1a(PyArrayObject*, type, i)
 NA_GET1b(PyArrayObject*, type, i)
 NA_GET1f(PyArrayObject*, type, i)
 NA_GET1(PyArrayObject*, type, i)
 NA_SET1a(PyArrayObject*, type, i, v)
 NA_SET1b(PyArrayObject*, type, i, v)
 NA_SET1f(PyArrayObject*, type, i, v)
 NA_SET1(PyArrayObject*, type, i, v)

14.5.1.3 Two index single element macros

 NA_GET2a(PyArrayObject*, type, i, j)
 NA_GET2b(PyArrayObject*, type, i, j)
 NA_GET2f(PyArrayObject*, type, i, j)
 NA_GET2(PyArrayObject*, type, i, j)
 NA_SET2a(PyArrayObject*, type, i, j, v)
 NA_SET2b(PyArrayObject*, type, i, j, v)
 NA_SET2f(PyArrayObject*, type, i, j, v)
 NA_SET2(PyArrayObject*, type, i, j, v)

14.5.1.4 One and Two Index, Offset, Float64/Complex64/Int64 functions

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:

  1. They're function calls, so they're a little more robust.
  2. 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

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)) {
                PyErr_Format(_Error, "Convolve1d: Invalid parameters.");
                goto _fail;
        }

        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)
                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;
}



Footnotes

... like:14.2
This function is also available as an example in the source distribution.
Send comments to the NumArray community.