***************** Beyond the Basics ***************** | The voyage of discovery is not in seeking new landscapes but in having | new eyes. | --- *Marcel Proust* | Discovery is seeing what everyone else has seen and thinking what no | one else has thought. | --- *Albert Szent-Gyorgi* Iterating over elements in the array ==================================== .. _`sec:array_iterator`: Basic Iteration --------------- One common algorithmic requirement is to be able to walk over all elements in a multidimensional array. The array iterator object makes this easy to do in a generic way that works for arrays of any dimension. Naturally, if you know the number of dimensions you will be using, then you can always write nested for loops to accomplish the iteration. If, however, you want to write code that works with any number of dimensions, then you can make use of the array iterator. An array iterator object is returned when accessing the .flat attribute of an array. .. index:: single: array iterator Basic usage is to call :cfunc:`PyArray_IterNew` ( ``array`` ) where array is an ndarray object (or one of its sub-classes). The returned object is an array-iterator object (the same object returned by the .flat attribute of the ndarray). This object is usually cast to PyArrayIterObject* so that its members can be accessed. The only members that are needed are ``iter->size`` which contains the total size of the array, ``iter->index``, which contains the current 1-d index into the array, and ``iter->dataptr`` which is a pointer to the data for the current element of the array. Sometimes it is also useful to access ``iter->ao`` which is a pointer to the underlying ndarray object. After processing data at the current element of the array, the next element of the array can be obtained using the macro :cfunc:`PyArray_ITER_NEXT` ( ``iter`` ). The iteration always proceeds in a C-style contiguous fashion (last index varying the fastest). The :cfunc:`PyArray_ITER_GOTO` ( ``iter``, ``destination`` ) can be used to jump to a particular point in the array, where ``destination`` is an array of npy_intp data-type with space to handle at least the number of dimensions in the underlying array. Occasionally it is useful to use :cfunc:`PyArray_ITER_GOTO1D` ( ``iter``, ``index`` ) which will jump to the 1-d index given by the value of ``index``. The most common usage, however, is given in the following example. .. code-block:: c PyObject *obj; /* assumed to be some ndarray object */ PyArrayIterObject *iter; ... iter = (PyArrayIterObject *)PyArray_IterNew(obj); if (iter == NULL) goto fail; /* Assume fail has clean-up code */ while (iter->index < iter->size) { /* do something with the data at it->dataptr */ PyArray_ITER_NEXT(it); } ... You can also use :cfunc:`PyArrayIter_Check` ( ``obj`` ) to ensure you have an iterator object and :cfunc:`PyArray_ITER_RESET` ( ``iter`` ) to reset an iterator object back to the beginning of the array. It should be emphasized at this point that you may not need the array iterator if your array is already contiguous (using an array iterator will work but will be slower than the fastest code you could write). The major purpose of array iterators is to encapsulate iteration over N-dimensional arrays with arbitrary strides. They are used in many, many places in the NumPy source code itself. If you already know your array is contiguous (Fortran or C), then simply adding the element- size to a running pointer variable will step you through the array very efficiently. In other words, code like this will probably be faster for you in the contiguous case (assuming doubles). .. code-block:: c npy_intp size; double *dptr; /* could make this any variable type */ size = PyArray_SIZE(obj); dptr = PyArray_DATA(obj); while(size--) { /* do something with the data at dptr */ dptr++; } Iterating over all but one axis ------------------------------- A common algorithm is to loop over all elements of an array and perform some function with each element by issuing a function call. As function calls can be time consuming, one way to speed up this kind of algorithm is to write the function so it takes a vector of data and then write the iteration so the function call is performed for an entire dimension of data at a time. This increases the amount of work done per function call, thereby reducing the function-call over-head to a small(er) fraction of the total time. Even if the interior of the loop is performed without a function call it can be advantageous to perform the inner loop over the dimension with the highest number of elements to take advantage of speed enhancements available on micro- processors that use pipelining to enhance fundmental operations. The :cfunc:`PyArray_IterAllButAxis` ( ``array``, ``&dim`` ) constructs an iterator object that is modified so that it will not iterate over the dimension indicated by dim. The only restriction on this iterator object, is that the :cfunc:`PyArray_Iter_GOTO1D` ( ``it``, ``ind`` ) macro cannot be used (thus flat indexing won't work either if you pass this object back to Python --- so you shouldn't do this). Note that the returned object from this routine is still usually cast to PyArrayIterObject \*. All that's been done is to modify the strides and dimensions of the returned iterator to simulate iterating over array[...,0,...] where 0 is placed on the :math:`\textrm{dim}^{\textrm{th}}` dimension. If dim is negative, then the dimension with the largest axis is found and used. Iterating over multiple arrays ------------------------------ Very often, it is desireable to iterate over several arrays at the same time. The universal functions are an example of this kind of behavior. If all you want to do is iterate over arrays with the same shape, then simply creating several iterator objects is the standard procedure. For example, the following code iterates over two arrays assumed to be the same shape and size (actually obj1 just has to have at least as many total elements as does obj2): .. code-block:: c /* It is already assumed that obj1 and obj2 are ndarrays of the same shape and size. */ iter1 = (PyArrayIterObject *)PyArray_IterNew(obj1); if (iter1 == NULL) goto fail; iter2 = (PyArrayIterObject *)PyArray_IterNew(obj2); if (iter2 == NULL) goto fail; /* assume iter1 is DECREF'd at fail */ while (iter2->index < iter2->size) { /* process with iter1->dataptr and iter2->dataptr */ PyArray_ITER_NEXT(iter1); PyArray_ITER_NEXT(iter2); } Broadcasting over multiple arrays --------------------------------- .. index:: single: broadcasting When multiple arrays are involved in an operation, you may want to use the same broadcasting rules that the math operations (*i.e.* the ufuncs) use. This can be done easily using the :ctype:`PyArrayMultiIterObject`. This is the object returned from the Python command numpy.broadcast and it is almost as easy to use from C. The function :cfunc:`PyArray_MultiIterNew` ( ``n``, ``...`` ) is used (with ``n`` input objects in place of ``...`` ). The input objects can be arrays or anything that can be converted into an array. A pointer to a PyArrayMultiIterObject is returned. Broadcasting has already been accomplished which adjusts the iterators so that all that needs to be done to advance to the next element in each array is for PyArray_ITER_NEXT to be called for each of the inputs. This incrementing is automatically performed by :cfunc:`PyArray_MultiIter_NEXT` ( ``obj`` ) macro (which can handle a multiterator ``obj`` as either a :ctype:`PyArrayMultiObject *` or a :ctype:`PyObject *`). The data from input number ``i`` is available using :cfunc:`PyArray_MultiIter_DATA` ( ``obj``, ``i`` ) and the total (broadcasted) size as :cfunc:`PyArray_MultiIter_SIZE` ( ``obj``). An example of using this feature follows. .. code-block:: c mobj = PyArray_MultiIterNew(2, obj1, obj2); size = PyArray_MultiIter_SIZE(obj); while(size--) { ptr1 = PyArray_MultiIter_DATA(mobj, 0); ptr2 = PyArray_MultiIter_DATA(mobj, 1); /* code using contents of ptr1 and ptr2 */ PyArray_MultiIter_NEXT(mobj); } The function :cfunc:`PyArray_RemoveLargest` ( ``multi`` ) can be used to take a multi-iterator object and adjust all the iterators so that iteration does not take place over the largest dimension (it makes that dimension of size 1). The code being looped over that makes use of the pointers will very-likely also need the strides data for each of the iterators. This information is stored in multi->iters[i]->strides. .. index:: single: array iterator There are several examples of using the multi-iterator in the NumPy source code as it makes N-dimensional broadcasting-code very simple to write. Browse the source for more examples. .. _`sec:Creating-a-new`: Creating a new universal function ================================= .. index:: pair: ufunc; adding new 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. .. 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`__ . *arg_types* (optional) If given, this should contain an array of integers of at least size ufunc.nargs containing the data-types expected by the loop function. The data will be copied into a NumPy-managed structure so the memory for this argument should be deleted after calling this function. If this is NULL, then it will be assumed that all data-types are of type usertype. *data* (optional) Specify any optional data needed by the function which will be passed when the function is called. .. index:: pair: dtype; adding new Subtyping the ndarray in C ========================== One of the lesser-used features that has been lurking in Python since 2.2 is the ability to sub-class types in C. This facility is one of the important reasons for basing NumPy off of the Numeric code-base which was already in C. A sub-type in C allows much more flexibility with regards to memory management. Sub-typing in C is not difficult even if you have only a rudimentary understanding of how to create new types for Python. While it is easiest to sub-type from a single parent type, sub-typing from multiple parent types is also possible. Multiple inheritence in C is generally less useful than it is in Python because a restriction on Python sub-types is that they have a binary compatible memory layout. Perhaps for this reason, it is somewhat easier to sub-type from a single parent type. .. index:: pair: ndarray; subtyping All C-structures corresponding to Python objects must begin with :cmacro:`PyObject_HEAD` (or :cmacro:`PyObject_VAR_HEAD`). In the same way, any sub-type must have a C-structure that begins with exactly the same memory layout as the parent type (or all of the parent types in the case of multiple-inheritance). The reason for this is that Python may attempt to access a member of the sub-type structure as if it had the parent structure ( *i.e.* it will cast a given pointer to a pointer to the parent structure and then dereference one of it's members). If the memory layouts are not compatible, then this attempt will cause unpredictable behavior (eventually leading to a memory violation and program crash). One of the elements in :cmacro:`PyObject_HEAD` is a pointer to a type-object structure. A new Python type is created by creating a new type-object structure and populating it with functions and pointers to describe the desired behavior of the type. Typically, a new C-structure is also created to contain the instance-specific information needed for each object of the type as well. For example, :cdata:`&PyArray_Type` is a pointer to the type-object table for the ndarray while a :ctype:`PyArrayObject *` variable is a pointer to a particular instance of an ndarray (one of the members of the ndarray structure is, in turn, a pointer to the type- object table :cdata:`&PyArray_Type`). Finally :cfunc:`PyType_Ready` () must be called for every new Python type. Creating sub-types ------------------ To create a sub-type, a similar proceedure must be followed except only behaviors that are different require new entries in the type- object structure. All other entires can be NULL and will be filled in by :cfunc:`PyType_Ready` with appropriate functions from the parent type(s). In particular, to create a sub-type in C follow these steps: 1. If needed create a new C-structure to handle each instance of your type. A typical C-structure would be: .. code-block:: c typedef _new_struct { PyArrayObject base; /* new things here */ } NewArrayObject; Notice that the full PyArrayObject is used as the first entry in order to ensure that the binary layout of instances of the new type is identical to the PyArrayObject. 2. Fill in a new Python type-object structure with pointers to new functions that will over-ride the default behavior while leaving any function that should remain the same unfilled (or NULL). The tp_name element should be different. 3. Fill in the tp_base member of the new type-object structure with a pointer to the (main) parent type object. For multiple-inheritance, also fill in the tp_bases member with a tuple containing all of the parent objects in the order they should be used to define inheritance. Remember, all parent-types must have the same C-structure for multiple inheritance to work properly. 4. Call :cfunc:`PyType_Ready` (). If this function returns a negative number, a failure occurred and the type is not initialized. Otherwise, the type is ready to be used. It is generally important to place a reference to the new type into the module dictionary so it can be accessed from Python. More information on creating sub-types in C can be learned by reading PEP 253 (available at http://www.python.org/dev/peps/pep-0253). Specific features of ndarray sub-typing --------------------------------------- Some special methods and attributes are used by arrays in order to facilitate the interoperation of sub-types with the base ndarray type. The __array_finalize\__ method ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. attribute:: ndarray.__array_finalize__ Several array-creation functions of the ndarray allow specification of a particular sub-type to be created. This allows sub-types to be handled seamlessly in many routines. When a sub-type is created in such a fashion, however, neither the __new_\_ method nor the __init\__ method gets called. Instead, the sub-type is allocated and the appropriate instance-structure members are filled in. Finally, the :obj:`__array_finalize__` attribute is looked-up in the object dictionary. If it is present and not None, then it can be either a CObject containing a pointer to a :cfunc:`PyArray_FinalizeFunc` or it can be a method taking a single argument (which could be None). If the :obj:`__array_finalize__` attribute is a CObject, then the pointer must be a pointer to a function with the signature: .. code-block:: c (int) (PyArrayObject *, PyObject *) The first argument is the newly created sub-type. The second argument (if not NULL) is the "parent" array (if the array was created using slicing or some other operation where a clearly-distinguishable parent is present). This routine can do anything it wants to. It should return a -1 on error and 0 otherwise. If the :obj:`__array_finalize__` attribute is not None nor a CObject, then it must be a Python method that takes the parent array as an argument (which could be None if there is no parent), and returns nothing. Errors in this method will be caught and handled. The __array_priority\__ attribute ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. attribute:: ndarray.__array_priority__ This attribute allows simple but flexible determination of which sub- type should be considered "primary" when an operation involving two or more sub-types arises. In operations where different sub-types are being used, the sub-type with the largest :obj:`__array_priority__` attribute will determine the sub-type of the output(s). If two sub- types have the same :obj:`__array_priority__` then the sub-type of the first argument determines the output. The default :obj:`__array_priority__` attribute returns a value of 0.0 for the base ndarray type and 1.0 for a sub-type. This attribute can also be defined by objects that are not sub-types of the ndarray and can be used to determine which :obj:`__array_wrap__` method should be called for the return output. The __array_wrap\__ method ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. attribute:: ndarray.__array_wrap__ Any class or type can define this method which should take an ndarray argument and return an instance of the type. It can be seen as the opposite of the :obj:`__array__` method. This method is used by the ufuncs (and other NumPy functions) to allow other objects to pass through. For Python >2.4, it can also be used to write a decorator that converts a function that works only with ndarrays to one that works with any type with :obj:`__array__` and :obj:`__array_wrap__` methods. .. index:: pair: ndarray; subtyping