summaryrefslogtreecommitdiff
path: root/doc/source/user/c-info.beyond-basics.rst
diff options
context:
space:
mode:
authorPauli Virtanen <pav@iki.fi>2008-11-23 10:39:05 +0000
committerPauli Virtanen <pav@iki.fi>2008-11-23 10:39:05 +0000
commit03582a3dbd1b4643b7b70cf277eada66dcb0800e (patch)
tree1cc0fa73843d5e602a561696817f41c4abfb811b /doc/source/user/c-info.beyond-basics.rst
parent638bacdd9212dd2258a886e0124cca56bf2b905d (diff)
parent84054e34dd58ceebc981d349d997e4dd7cd7c80c (diff)
downloadnumpy-03582a3dbd1b4643b7b70cf277eada66dcb0800e.tar.gz
Moved numpy-docs under doc/ in the main Numpy trunk.
Diffstat (limited to 'doc/source/user/c-info.beyond-basics.rst')
-rw-r--r--doc/source/user/c-info.beyond-basics.rst734
1 files changed, 734 insertions, 0 deletions
diff --git a/doc/source/user/c-info.beyond-basics.rst b/doc/source/user/c-info.beyond-basics.rst
new file mode 100644
index 000000000..905ab67eb
--- /dev/null
+++ b/doc/source/user/c-info.beyond-basics.rst
@@ -0,0 +1,734 @@
+*****************
+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<n; i++) {
+ *((double *)op) = *((double *)i1) + \
+ *((double *)i2);
+ i1 += is1; i2 += is2; op += os;
+ }
+ }
+
+ *data*
+
+ An array of data. There should be ntypes entries (or NULL) --- one for
+ every loop function defined for this ufunc. This data will be passed
+ in to the 1-d loop. One common use of this data variable is to pass in
+ an actual function to call to compute the result when a generic 1-d
+ loop (e.g. :cfunc:`PyUFunc_d_d`) is being used.
+
+ *types*
+
+ An array of type-number signatures (type ``char`` ). This
+ array should be of size (nin+nout)*ntypes and contain the
+ data-types for the corresponding 1-d loop. The inputs should
+ be first followed by the outputs. For example, suppose I have
+ a ufunc that supports 1 integer and 1 double 1-d loop
+ (length-2 func and data arrays) that takes 2 inputs and
+ returns 1 output that is always a complex double, then the
+ types array would be
+
+
+ The bit-width names can also be used (e.g. :cdata:`NPY_INT32`,
+ :cdata:`NPY_COMPLEX128` ) if desired.
+
+ *ntypes*
+
+ The number of data-types supported. This is equal to the number of 1-d
+ loops provided.
+
+ *nin*
+
+ The number of input arguments.
+
+ *nout*
+
+ The number of output arguments.
+
+ *identity*
+
+ Either :cdata:`PyUFunc_One`, :cdata:`PyUFunc_Zero`, :cdata:`PyUFunc_None`.
+ This specifies what should be returned when an empty array is
+ passed to the reduce method of the ufunc.
+
+ *name*
+
+ A ``NULL`` -terminated string providing the name of this ufunc
+ (should be the Python name it will be called).
+
+ *doc*
+
+ A documentation string for this ufunc (will be used in generating the
+ response to ``{ufunc_name}.__doc__``). Do not include the function
+ signature or the name as this is generated automatically.
+
+ *check_return*
+
+ Not presently used, but this integer value does get set in the
+ structure-member of similar name.
+
+ .. index::
+ pair: ufunc; adding new
+
+ The returned ufunc object is a callable Python object. It should be
+ placed in a (module) dictionary under the same name as was used in the
+ name argument to the ufunc-creation routine. The following example is
+ adapted from the umath module
+
+ .. code-block:: c
+
+ static PyUFuncGenericFunction atan2_functions[]=\
+ {PyUFunc_ff_f, PyUFunc_dd_d,
+ PyUFunc_gg_g, PyUFunc_OO_O_method};
+ static void* atan2_data[]=\
+ {(void *)atan2f,(void *) atan2,
+ (void *)atan2l,(void *)"arctan2"};
+ static char atan2_signatures[]=\
+ {NPY_FLOAT, NPY_FLOAT, NPY_FLOAT,
+ NPY_DOUBLE, NPY_DOUBLE,
+ NPY_DOUBLE, NPY_LONGDOUBLE,
+ NPY_LONGDOUBLE, NPY_LONGDOUBLE
+ NPY_OBJECT, NPY_OBJECT,
+ NPY_OBJECT};
+ ...
+ /* in the module initialization code */
+ PyObject *f, *dict, *module;
+ ...
+ dict = PyModule_GetDict(module);
+ ...
+ f = PyUFunc_FromFuncAndData(atan2_functions,
+ atan2_data, atan2_signatures, 4, 2, 1,
+ PyUFunc_None, "arctan2",
+ "a safe and correct arctan(x1/x2)", 0);
+ PyDict_SetItemString(dict, "arctan2", f);
+ Py_DECREF(f);
+ ...
+
+
+User-defined data-types
+=======================
+
+NumPy comes with 21 builtin data-types. While this covers a large
+majority of possible use cases, it is conceivable that a user may have
+a need for an additional data-type. There is some support for adding
+an additional data-type into the NumPy system. This additional data-
+type will behave much like a regular data-type except ufuncs must have
+1-d loops registered to handle it separately. Also checking for
+whether or not other data-types can be cast "safely" to and from this
+new type or not will always return "can cast" unless you also register
+which types your new data-type can be cast to and from. Adding
+data-types is one of the less well-tested areas for NumPy 1.0, so
+there may be bugs remaining in the approach. Only add a new data-type
+if you can't do what you want to do using the OBJECT or VOID
+data-types that are already available. As an example of what I
+consider a useful application of the ability to add data-types is the
+possibility of adding a data-type of arbitrary precision floats to
+NumPy.
+
+.. index::
+ pair: dtype; adding new
+
+
+Adding the new data-type
+------------------------
+
+To begin to make use of the new data-type, you need to first define a
+new Python type to hold the scalars of your new data-type. It should
+be acceptable to inherit from one of the array scalars if your new
+type has a binary compatible layout. This will allow your new data
+type to have the methods and attributes of array scalars. New data-
+types must have a fixed memory size (if you want to define a data-type
+that needs a flexible representation, like a variable-precision
+number, then use a pointer to the object as the data-type). The memory
+layout of the object structure for the new Python type must be
+PyObject_HEAD followed by the fixed-size memory needed for the data-
+type. For example, a suitable structure for the new Python type is:
+
+.. code-block:: c
+
+ typedef struct {
+ PyObject_HEAD;
+ some_data_type obval;
+ /* the name can be whatever you want */
+ } PySomeDataTypeObject;
+
+After you have defined a new Python type object, you must then define
+a new :ctype:`PyArray_Descr` structure whose typeobject member will contain a
+pointer to the data-type you've just defined. In addition, the
+required functions in the ".f" member must be defined: nonzero,
+copyswap, copyswapn, setitem, getitem, and cast. The more functions in
+the ".f" member you define, however, the more useful the new data-type
+will be. It is very important to intialize unused functions to NULL.
+This can be achieved using :cfunc:`PyArray_InitArrFuncs` (f).
+
+Once a new :ctype:`PyArray_Descr` structure is created and filled with the
+needed information and useful functions you call
+:cfunc:`PyArray_RegisterDataType` (new_descr). The return value from this
+call is an integer providing you with a unique type_number that
+specifies your data-type. This type number should be stored and made
+available by your module so that other modules can use it to recognize
+your data-type (the other mechanism for finding a user-defined
+data-type number is to search based on the name of the type-object
+associated with the data-type using :cfunc:`PyArray_TypeNumFromName` ).
+
+
+Registering a casting function
+------------------------------
+
+You may want to allow builtin (and other user-defined) data-types to
+be cast automatically to your data-type. In order to make this
+possible, you must register a casting function with the data-type you
+want to be able to cast from. This requires writing low-level casting
+functions for each conversion you want to support and then registering
+these functions with the data-type descriptor. A low-level casting
+function has the signature.
+
+.. cfunction:: void castfunc( void* from, void* to, npy_intp n, void* fromarr, void* toarr)
+
+ Cast ``n`` elements ``from`` one type ``to`` another. The data to
+ cast from is in a contiguous, correctly-swapped and aligned chunk
+ of memory pointed to by from. The buffer to cast to is also
+ contiguous, correctly-swapped and aligned. The fromarr and toarr
+ arguments should only be used for flexible-element-sized arrays
+ (string, unicode, void).
+
+An example castfunc is:
+
+.. code-block:: c
+
+ static void
+ double_to_float(double *from, float* to, npy_intp n,
+ void* ig1, void* ig2);
+ while (n--) {
+ (*to++) = (double) *(from++);
+ }
+
+This could then be registered to convert doubles to floats using the
+code:
+
+.. code-block:: c
+
+ doub = PyArray_DescrFromType(NPY_DOUBLE);
+ PyArray_RegisterCastFunc(doub, NPY_FLOAT,
+ (PyArray_VectorUnaryFunc *)double_to_float);
+ Py_DECREF(doub);
+
+
+Registering coercion rules
+--------------------------
+
+By default, all user-defined data-types are not presumed to be safely
+castable to any builtin data-types. In addition builtin data-types are
+not presumed to be safely castable to user-defined data-types. This
+situation limits the ability of user-defined data-types to participate
+in the coercion system used by ufuncs and other times when automatic
+coercion takes place in NumPy. This can be changed by registering
+data-types as safely castable from a particlar data-type object. The
+function :cfunc:`PyArray_RegisterCanCast` (from_descr, totype_number,
+scalarkind) should be used to specify that the data-type object
+from_descr can be cast to the data-type with type number
+totype_number. If you are not trying to alter scalar coercion rules,
+then use :cdata:`PyArray_NOSCALAR` for the scalarkind argument.
+
+If you want to allow your new data-type to also be able to share in
+the scalar coercion rules, then you need to specify the scalarkind
+function in the data-type object's ".f" member to return the kind of
+scalar the new data-type should be seen as (the value of the scalar is
+available to that function). Then, you can register data-types that
+can be cast to separately for each scalar kind that may be returned
+from your user-defined data-type. If you don't register scalar
+coercion handling, then all of your user-defined data-types will be
+seen as :cdata:`PyArray_NOSCALAR`.
+
+
+Registering a ufunc loop
+------------------------
+
+You may also want to register low-level ufunc loops for your data-type
+so that an ndarray of your data-type can have math applied to it
+seamlessly. Registering a new loop with exactly the same arg_types
+signature, silently replaces any previously registered loops for that
+data-type.
+
+Before you can register a 1-d loop for a ufunc, the ufunc must be
+previously created. Then you call :cfunc:`PyUFunc_RegisterLoopForType`
+(...) with the information needed for the loop. The return value of
+this function is ``0`` if the process was successful and ``-1`` with
+an error condition set if it was not successful.
+
+.. cfunction:: int PyUFunc_RegisterLoopForType( PyUFuncObject* ufunc, int usertype, PyUFuncGenericFunction function, int* arg_types, void* data)
+
+ *ufunc*
+
+ The ufunc to attach this loop to.
+
+ *usertype*
+
+ The user-defined type this loop should be indexed under. This number
+ must be a user-defined type or an error occurs.
+
+ *function*
+
+ The ufunc inner 1-d loop. This function must have the signature as
+ explained in Section `3 <#sec-creating-a-new>`__ .
+
+ *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` (<pointer_to_type_object>) 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` (<pointer_to_new_type>). 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.
+
+.. note:: XXX: some of the documentation below needs to be moved to the
+ reference guide.
+
+
+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