/* -*- c -*- */
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "structmember.h"


#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
#define _NPY_NO_DEPRECATIONS /* for NPY_CHAR */

#include "numpy/npy_common.h"
#include "numpy/arrayobject.h"
#include "numpy/arrayscalars.h"
#include "npy_pycompat.h"
#include "numpy/npy_math.h"
#include "numpy/halffloat.h"

#include "npy_config.h"
#include "npy_sort.h"
#include "common.h"
#include "ctors.h"
#include "lowlevel_strided_loops.h"
#include "usertypes.h"
#include "_datetime.h"
#include "arrayobject.h"
#include "alloc.h"
#ifdef NPY_HAVE_SSE2_INTRINSICS
#include <emmintrin.h>
#endif

#include "numpyos.h"
#include <string.h>

#include "cblasfuncs.h"
#include "npy_cblas.h"
#include <limits.h>
#include <assert.h>

/* check for sequences, but ignore the types numpy considers scalars */
static NPY_INLINE npy_bool
PySequence_NoString_Check(PyObject *op) {
    return
        PySequence_Check(op) &&
        !PyString_Check(op) &&
        !PyUnicode_Check(op) &&
        !PyArray_IsZeroDim(op);
}

/*
 *****************************************************************************
 **                        PYTHON TYPES TO C TYPES                          **
 *****************************************************************************
 */

static double
MyPyFloat_AsDouble(PyObject *obj)
{
    double ret = 0;
    PyObject *num;

    if (obj == Py_None) {
        return NPY_NAN;
    }
    num = PyNumber_Float(obj);
    if (num == NULL) {
        return NPY_NAN;
    }
    ret = PyFloat_AsDouble(num);
    Py_DECREF(num);
    return ret;
}

static npy_half
MyPyFloat_AsHalf(PyObject *obj)
{
    return npy_double_to_half(MyPyFloat_AsDouble(obj));
}

static PyObject *
MyPyFloat_FromHalf(npy_half h)
{
    return PyFloat_FromDouble(npy_half_to_double(h));
}

/* Handle case of assigning from an array scalar in setitem */
static int
convert_to_scalar_and_retry(PyObject *op, void *ov, void *vap,
                      int (*setitem)(PyObject *op, void *ov, void *vap))
{
    PyObject *temp;

    assert(PyArray_IsZeroDim(op));
    temp = PyArray_ToScalar(PyArray_BYTES((PyArrayObject *)op),
                                      (PyArrayObject *)op);
    if (temp == NULL) {
        return -1;
    }
    else {
        int res = setitem(temp, ov, vap);
        Py_DECREF(temp);
        return res;
    }
}


/**begin repeat
 *
 * #Type = Long, LongLong#
 * #type = npy_long, npy_longlong#
 */
static @type@
MyPyLong_As@Type@ (PyObject *obj)
{
    @type@ ret;
    PyObject *num = PyNumber_Long(obj);

    if (num == NULL) {
        return -1;
    }
    ret = PyLong_As@Type@(num);
    Py_DECREF(num);
    return ret;
}

/**end repeat**/

/**begin repeat
 *
 * #Type = Long, LongLong#
 * #type = npy_ulong, npy_ulonglong#
 */
static @type@
MyPyLong_AsUnsigned@Type@ (PyObject *obj)
{
    @type@ ret;
    PyObject *num = PyNumber_Long(obj);

    if (num == NULL) {
        return -1;
    }
    ret = PyLong_AsUnsigned@Type@(num);
    if (PyErr_Occurred()) {
        PyErr_Clear();
        ret = PyLong_As@Type@(num);
    }
    Py_DECREF(num);
    return ret;
}

/**end repeat**/

static npy_longlong
npy_strtoll(const char *str, char **endptr, int base)
{
#if defined HAVE_STRTOLL
    return strtoll(str, endptr, base);
#elif defined _MSC_VER
    return _strtoi64(str, endptr, base);
#else
    /* ok on 64 bit posix */
    return PyOS_strtol(str, endptr, base);
#endif
}

static npy_ulonglong
npy_strtoull(const char *str, char **endptr, int base)
{
#if defined HAVE_STRTOULL
    return strtoull(str, endptr, base);
#elif defined _MSC_VER
    return _strtoui64(str, endptr, base);
#else
    /* ok on 64 bit posix */
    return PyOS_strtoul(str, endptr, base);
#endif
}

/*
 *****************************************************************************
 **                         GETITEM AND SETITEM                             **
 *****************************************************************************
 */


/**begin repeat
 *
 * #TYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, LONG, UINT, ULONG,
 *         LONGLONG, ULONGLONG, HALF, FLOAT, DOUBLE#
 * #func1 = PyBool_FromLong, PyInt_FromLong*6, PyLong_FromUnsignedLong*2,
 *          PyLong_FromLongLong, PyLong_FromUnsignedLongLong,
 *          MyPyFloat_FromHalf, PyFloat_FromDouble*2#
 * #func2 = PyObject_IsTrue, MyPyLong_AsLong*6, MyPyLong_AsUnsignedLong*2,
 *          MyPyLong_AsLongLong, MyPyLong_AsUnsignedLongLong,
 *          MyPyFloat_AsHalf, MyPyFloat_AsDouble*2#
 * #type = npy_bool,
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int,
 *         npy_long, npy_uint, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double#
 * #type1 = long*7, npy_ulong*2, npy_longlong, npy_ulonglong,
 *          npy_half, npy_float, npy_double#
 * #kind = Bool, Byte, UByte, Short, UShort, Int, Long, UInt, ULong,
 *         LongLong, ULongLong, Half, Float, Double#
*/
static PyObject *
@TYPE@_getitem(void *input, void *vap)
{
    PyArrayObject *ap = vap;
    char *ip = input;
    @type@ t1;

    if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) {
        t1 = *((@type@ *)ip);
        return @func1@((@type1@)t1);
    }
    else {
        PyArray_DESCR(ap)->f->copyswap(&t1, ip, PyArray_ISBYTESWAPPED(ap), ap);
        return @func1@((@type1@)t1);
    }
}

static int
@TYPE@_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    @type@ temp;  /* ensures alignment */

    if (PyArray_IsScalar(op, @kind@)) {
        temp = ((Py@kind@ScalarObject *)op)->obval;
    }
    else {
        temp = (@type@)@func2@(op);
    }
    if (PyErr_Occurred()) {
        PyObject *type, *value, *traceback;
        PyErr_Fetch(&type, &value, &traceback);
        if (PySequence_NoString_Check(op)) {
            PyErr_SetString(PyExc_ValueError,
                    "setting an array element with a sequence.");
            Py_DECREF(type);
            Py_XDECREF(value);
            Py_XDECREF(traceback);
        }
        else {
            PyErr_Restore(type, value, traceback);
        }
        return -1;
    }
    if (ap == NULL || PyArray_ISBEHAVED(ap))
        *((@type@ *)ov)=temp;
    else {
        PyArray_DESCR(ap)->f->copyswap(ov, &temp, PyArray_ISBYTESWAPPED(ap),
                                       ap);
    }
    return 0;
}

/**end repeat**/

/**begin repeat
 *
 * #TYPE = CFLOAT, CDOUBLE#
 * #type = npy_float, npy_double#
 */
static PyObject *
@TYPE@_getitem(void *input, void *vap)
{
    PyArrayObject *ap = vap;
    char *ip = input;
    @type@ t1, t2;

    if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) {
        return PyComplex_FromDoubles((double)((@type@ *)ip)[0],
                (double)((@type@ *)ip)[1]);
    }
    else {
        int size = sizeof(@type@);

        npy_bool swap = PyArray_ISBYTESWAPPED(ap);
        copy_and_swap(&t1, ip, size, 1, 0, swap);
        copy_and_swap(&t2, ip + size, size, 1, 0, swap);
        return PyComplex_FromDoubles((double)t1, (double)t2);
    }
}

/**end repeat**/



/**begin repeat
 *
 * #NAME = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 * #ftype = npy_float, npy_double, npy_longdouble#
 * #kind = CFloat, CDouble, CLongDouble#
 */
static int
@NAME@_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    Py_complex oop;
    @type@ temp;
    int rsize;

    if (PyArray_IsZeroDim(op)) {
        return convert_to_scalar_and_retry(op, ov, vap, @NAME@_setitem);
    }

    if (PyArray_IsScalar(op, @kind@)){
        temp = ((Py@kind@ScalarObject *)op)->obval;
    }
    else {
        if (op == Py_None) {
            oop.real = NPY_NAN;
            oop.imag = NPY_NAN;
        }
        else {
            oop = PyComplex_AsCComplex (op);
            if (PyErr_Occurred()) {
                return -1;
            }
        }
        temp.real = (@ftype@) oop.real;
        temp.imag = (@ftype@) oop.imag;
    }

    memcpy(ov, &temp, PyArray_DESCR(ap)->elsize);
    if (PyArray_ISBYTESWAPPED(ap)) {
        byte_swap_vector(ov, 2, sizeof(@ftype@));
    }
    rsize = sizeof(@ftype@);
    copy_and_swap(ov, &temp, rsize, 2, rsize, PyArray_ISBYTESWAPPED(ap));
    return 0;
}

/**end repeat**/

static NPY_INLINE npy_longdouble
string_to_long_double(PyObject*op)
{
    char *s;
    char *end;
    npy_longdouble temp;
    PyObject* b;

    if (PyUnicode_Check(op)) {
        b = PyUnicode_AsUTF8String(op);
        if (!b) {
            return 0;
        }
    }
    else {
        b = op;
        Py_XINCREF(b);
    }
    s = PyBytes_AsString(b);
    if (s) {
        errno = 0;
        temp = NumPyOS_ascii_strtold(s, &end);
        if (errno == ERANGE) {
           if (PyErr_Warn(PyExc_RuntimeWarning,
                   "overflow encountered in conversion from string") < 0) {
               Py_XDECREF(b);
               return 0;
           }
           /* strtold returns INFINITY of the correct sign. */
        }
        else if (errno) {
            PyErr_Format(PyExc_ValueError,
                         "invalid literal for long double: %s (%s)",
                         s,
                         strerror(errno));
            Py_XDECREF(b);
            return 0;
        }

        /* Extra characters at the end of the string, or nothing parsed */
        if (end == s || *end) {
            PyErr_Format(PyExc_ValueError,
                         "invalid literal for long double: %s",
                         s);
            Py_XDECREF(b);
            return 0;
        }
        Py_XDECREF(b);
    }
    else {
        /* Probably wasn't a string, try converting it via a python double */
        PyErr_Clear();
        Py_XDECREF(b);
        temp = (npy_longdouble) MyPyFloat_AsDouble(op);
    }
    return temp;
}

/*
 * These return array scalars which are different than other date-types.
 */

static PyObject *
LONGDOUBLE_getitem(void *ip, void *ap)
{
    return PyArray_Scalar(ip, PyArray_DESCR((PyArrayObject *)ap), NULL);
}

static int
LONGDOUBLE_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    /* ensure alignment */
    npy_longdouble temp;

    if (PyArray_IsZeroDim(op)) {
        return convert_to_scalar_and_retry(op, ov, vap, LONGDOUBLE_setitem);
    }

    if (PyArray_IsScalar(op, LongDouble)) {
        temp = ((PyLongDoubleScalarObject *)op)->obval;
    }
    else {
        /* In case something funny happened in PyArray_IsScalar */
        if (PyErr_Occurred()) {
            return -1;
        }
        temp = string_to_long_double(op);
    }
    if (PyErr_Occurred()) {
        return -1;
    }
    if (ap == NULL || PyArray_ISBEHAVED(ap)) {
        *((npy_longdouble *)ov) = temp;
    }
    else {
        copy_and_swap(ov, &temp, PyArray_DESCR(ap)->elsize, 1, 0,
                      PyArray_ISBYTESWAPPED(ap));
    }
    return 0;
}

static PyObject *
CLONGDOUBLE_getitem(void *ip, void *ap)
{
    return PyArray_Scalar(ip, PyArray_DESCR((PyArrayObject *)ap), NULL);
}

/* UNICODE */
static PyObject *
UNICODE_getitem(void *ip, void *vap)
{
    PyArrayObject *ap = vap;
    Py_ssize_t size = PyArray_ITEMSIZE(ap);
    int swap = PyArray_ISBYTESWAPPED(ap);
    int align = !PyArray_ISALIGNED(ap);

    return (PyObject *)PyUnicode_FromUCS4(ip, size, swap, align);
}

static int
UNICODE_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    PyObject *temp;
    Py_UNICODE *ptr;
    int datalen;
#ifndef Py_UNICODE_WIDE
    char *buffer;
#endif

    if (PyArray_IsZeroDim(op)) {
        return convert_to_scalar_and_retry(op, ov, vap, UNICODE_setitem);
    }

    if (PySequence_NoString_Check(op)) {
        PyErr_SetString(PyExc_ValueError,
                "setting an array element with a sequence");
        return -1;
    }
#if defined(NPY_PY3K)
    if (PyBytes_Check(op)) {
        /* Try to decode from ASCII */
        temp = PyUnicode_FromEncodedObject(op, "ASCII", "strict");
        if (temp == NULL) {
            return -1;
        }
    }
    else if ((temp=PyObject_Str(op)) == NULL) {
#else
    if ((temp=PyObject_Unicode(op)) == NULL) {
#endif
        return -1;
    }
    ptr = PyUnicode_AS_UNICODE(temp);
    if ((ptr == NULL) || (PyErr_Occurred())) {
        Py_DECREF(temp);
        return -1;
    }
    datalen = PyUnicode_GET_DATA_SIZE(temp);

#ifdef Py_UNICODE_WIDE
    memcpy(ov, ptr, PyArray_MIN(PyArray_DESCR(ap)->elsize, datalen));
#else
    if (!PyArray_ISALIGNED(ap)) {
        buffer = PyArray_malloc(PyArray_DESCR(ap)->elsize);
        if (buffer == NULL) {
            Py_DECREF(temp);
            PyErr_NoMemory();
            return -1;
        }
    }
    else {
        buffer = ov;
    }
    datalen = PyUCS2Buffer_AsUCS4(ptr, (npy_ucs4 *)buffer,
            datalen >> 1, PyArray_DESCR(ap)->elsize >> 2);
    datalen <<= 2;
    if (!PyArray_ISALIGNED(ap)) {
        memcpy(ov, buffer, datalen);
        PyArray_free(buffer);
    }
#endif
    /* Fill in the rest of the space with 0 */
    if (PyArray_DESCR(ap)->elsize > datalen) {
        memset((char*)ov + datalen, 0, (PyArray_DESCR(ap)->elsize - datalen));
    }
    if (PyArray_ISBYTESWAPPED(ap)) {
        byte_swap_vector(ov, PyArray_DESCR(ap)->elsize >> 2, 4);
    }
    Py_DECREF(temp);
    return 0;
}

/* STRING
 *
 * can handle both NULL-terminated and not NULL-terminated cases
 * will truncate all ending NULLs in returned string.
 */
static PyObject *
STRING_getitem(void *ip, void *vap)
{
    PyArrayObject *ap = vap;
    /* Will eliminate NULLs at the end */
    char *ptr;
    int size = PyArray_DESCR(ap)->elsize;

    ptr = (char *)ip + size - 1;
    while (size > 0 && *ptr-- == '\0') {
        size--;
    }
    return PyBytes_FromStringAndSize(ip,size);
}

static int
STRING_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    char *ptr;
    Py_ssize_t len;
    PyObject *temp = NULL;

    if (PyArray_IsZeroDim(op)) {
        return convert_to_scalar_and_retry(op, ov, vap, STRING_setitem);
    }

    if (PySequence_NoString_Check(op)) {
        PyErr_SetString(PyExc_ValueError,
                "setting an array element with a sequence");
        return -1;
    }
#if defined(NPY_PY3K)
    if (PyUnicode_Check(op)) {
        /* Assume ASCII codec -- function similarly as Python 2 */
        temp = PyUnicode_AsASCIIString(op);
        if (temp == NULL) {
            return -1;
        }
    }
    else if (PyBytes_Check(op) || PyMemoryView_Check(op)) {
        temp = PyObject_Bytes(op);
        if (temp == NULL) {
            return -1;
        }
    }
    else {
        /* Emulate similar casting behavior as on Python 2 */
        PyObject *str;
        str = PyObject_Str(op);
        if (str == NULL) {
            return -1;
        }
        temp = PyUnicode_AsASCIIString(str);
        Py_DECREF(str);
        if (temp == NULL) {
            return -1;
        }
    }
#else
    if ((temp = PyObject_Str(op)) == NULL) {
        return -1;
    }
#endif
    if (PyBytes_AsStringAndSize(temp, &ptr, &len) < 0) {
        Py_DECREF(temp);
        return -1;
    }
    memcpy(ov, ptr, PyArray_MIN(PyArray_DESCR(ap)->elsize,len));
    /*
     * If string length is smaller than room in array
     * Then fill the rest of the element size with NULL
     */
    if (PyArray_DESCR(ap)->elsize > len) {
        memset((char *)ov + len, 0, (PyArray_DESCR(ap)->elsize - len));
    }
    Py_DECREF(temp);
    return 0;
}

/* OBJECT */

#define __ALIGNED(obj, sz) ((((size_t) obj) % (sz))==0)

static PyObject *
OBJECT_getitem(void *ip, void *NPY_UNUSED(ap))
{
    PyObject *obj;
    NPY_COPY_PYOBJECT_PTR(&obj, ip);
    if (obj == NULL) {
        Py_RETURN_NONE;
    }
    else {
        Py_INCREF(obj);
        return obj;
    }
}


static int
OBJECT_setitem(PyObject *op, void *ov, void *NPY_UNUSED(ap))
{
    PyObject *obj;

    NPY_COPY_PYOBJECT_PTR(&obj, ov);

    Py_INCREF(op);
    Py_XDECREF(obj);

    NPY_COPY_PYOBJECT_PTR(ov, &op);

    return PyErr_Occurred() ? -1 : 0;
}

/* VOID */

static PyObject *
VOID_getitem(void *input, void *vap)
{
    PyArrayObject *ap = vap;
    char *ip = input;
    PyArrayObject *u = NULL;
    PyArray_Descr* descr;
    int itemsize;

    descr = PyArray_DESCR(ap);
    if (PyDataType_HASFIELDS(descr)) {
        PyObject *key;
        PyObject *names;
        int i, n;
        PyObject *ret;
        PyObject *tup;
        int savedflags;

        /* get the names from the fields dictionary*/
        names = descr->names;
        n = PyTuple_GET_SIZE(names);
        ret = PyTuple_New(n);
        savedflags = PyArray_FLAGS(ap);
        for (i = 0; i < n; i++) {
            npy_intp offset;
            PyArray_Descr *new;
            key = PyTuple_GET_ITEM(names, i);
            tup = PyDict_GetItem(descr->fields, key);
            if (_unpack_field(tup, &new, &offset) < 0) {
                Py_DECREF(ret);
                ((PyArrayObject_fields *)ap)->descr = descr;
                return NULL;
            }
            /*
             * TODO: temporarily modifying the array like this
             *       is bad coding style, should be changed.
             */
            ((PyArrayObject_fields *)ap)->descr = new;
            /* update alignment based on offset */
            if ((new->alignment > 1)
                    && ((((npy_intp)(ip+offset)) % new->alignment) != 0)) {
                PyArray_CLEARFLAGS(ap, NPY_ARRAY_ALIGNED);
            }
            else {
                PyArray_ENABLEFLAGS(ap, NPY_ARRAY_ALIGNED);
            }
            PyTuple_SET_ITEM(ret, i, PyArray_GETITEM(ap, ip+offset));
            ((PyArrayObject_fields *)ap)->flags = savedflags;
        }
        ((PyArrayObject_fields *)ap)->descr = descr;
        return ret;
    }

    if (descr->subarray) {
        /* return an array of the basic type */
        PyArray_Dims shape = {NULL, -1};
        PyArrayObject *ret;

        if (!(PyArray_IntpConverter(descr->subarray->shape, &shape))) {
            npy_free_cache_dim_obj(shape);
            PyErr_SetString(PyExc_ValueError,
                    "invalid shape in fixed-type tuple.");
            return NULL;
        }
        Py_INCREF(descr->subarray->base);
        ret = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type,
                descr->subarray->base, shape.len, shape.ptr,
                NULL, ip, PyArray_FLAGS(ap)&(~NPY_ARRAY_F_CONTIGUOUS), NULL);
        npy_free_cache_dim_obj(shape);
        if (!ret) {
            return NULL;
        }
        Py_INCREF(ap);
        if (PyArray_SetBaseObject(ret, (PyObject *)ap) < 0) {
            Py_DECREF(ret);
            return NULL;
        }
        PyArray_UpdateFlags((PyArrayObject *)ret, NPY_ARRAY_UPDATE_ALL);
        return (PyObject *)ret;
    }

    /* 2017-11-26, 1.14 */
    if (DEPRECATE_FUTUREWARNING(
            "the `.item()` method of unstructured void types will return an "
            "immutable `bytes` object in the near future, the same as "
            "returned by `bytes(void_obj)`, instead of the mutable memoryview "
            "or integer array returned in numpy 1.13.") < 0) {
        return NULL;
    }
    /*
     * In the future all the code below will be replaced by
     *
     *       For unstructured void types like V4, return a bytes object (copy).
     *     return PyBytes_FromStringAndSize(PyArray_DATA(ap), descr->elsize);
     */

    if (PyDataType_FLAGCHK(descr, NPY_ITEM_HASOBJECT)
            || PyDataType_FLAGCHK(descr, NPY_ITEM_IS_POINTER)) {
        PyErr_SetString(PyExc_ValueError,
                "tried to get void-array with object members as buffer.");
        return NULL;
    }
    itemsize = PyArray_DESCR(ap)->elsize;

#if defined(NPY_PY3K)
    /*
     * Return a byte array; there are no plain buffer objects on Py3
     */
    {
        npy_intp dims[1], strides[1];
        dims[0] = itemsize;
        strides[0] = 1;
        descr = PyArray_DescrNewFromType(NPY_BYTE);
        u = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type,
                             descr, 1, dims, strides, ip,
                             PyArray_ISWRITEABLE(ap) ? NPY_ARRAY_WRITEABLE : 0,
                             NULL);
        Py_INCREF(ap);
        if (PyArray_SetBaseObject(u, (PyObject *)ap) < 0) {
            Py_DECREF(u);
            return NULL;
        }
    }
#else
    /*
     * default is to return buffer object pointing to
     * current item a view of it
     */
    if (PyArray_ISWRITEABLE(ap)) {
        if (array_might_be_written(ap) < 0) {
            return NULL;
        }
        u = (PyArrayObject *)PyBuffer_FromReadWriteMemory(ip, itemsize);
    }
    else {
        u = (PyArrayObject *)PyBuffer_FromMemory(ip, itemsize);
    }
#endif

    if (u == NULL) {
        return NULL;
    }
    return (PyObject *)u;
}


NPY_NO_EXPORT int PyArray_CopyObject(PyArrayObject *, PyObject *);

/* Given a structured PyArrayObject arr, index i and structured datatype descr,
 * modify the dtype of arr to contain a single field corresponding to the ith
 * field of descr, recompute the alignment flag, and return the offset of the
 * field (in offset_p). This is useful in preparation for calling copyswap on
 * individual fields of a numpy structure, in VOID_setitem.  Compare to inner
 * loops in VOID_getitem and VOID_nonzero.
 *
 * WARNING: Clobbers arr's dtype and alignment flag.
 */
NPY_NO_EXPORT int
_setup_field(int i, PyArray_Descr *descr, PyArrayObject *arr,
            npy_intp *offset_p)
{
    PyObject *key;
    PyObject *tup;
    PyArray_Descr *new;
    npy_intp offset;

    key = PyTuple_GET_ITEM(descr->names, i);
    tup = PyDict_GetItem(descr->fields, key);
    if (_unpack_field(tup, &new, &offset) < 0) {
        return -1;
    }

    ((PyArrayObject_fields *)(arr))->descr = new;
    if ((new->alignment > 1) && ((offset % new->alignment) != 0)) {
        PyArray_CLEARFLAGS(arr, NPY_ARRAY_ALIGNED);
    }
    else {
        PyArray_ENABLEFLAGS(arr, NPY_ARRAY_ALIGNED);
    }

    *offset_p = offset;
    return 0;
}

/* Helper function for VOID_setitem, which uses the copyswap or casting code to
 * copy structured datatypes between numpy arrays or scalars.
 */
static int
_copy_and_return_void_setitem(PyArray_Descr *dstdescr, char *dstdata,
                              PyArray_Descr *srcdescr, char *srcdata){
    PyArrayObject_fields dummy_struct;
    PyArrayObject *dummy = (PyArrayObject *)&dummy_struct;
    npy_int names_size = PyTuple_GET_SIZE(dstdescr->names);
    npy_intp offset;
    npy_int i;
    int ret;

    /* Fast path if dtypes are equal */
    if (PyArray_EquivTypes(srcdescr, dstdescr)) {
        for (i = 0; i < names_size; i++) {
            /* neither line can ever fail, in principle */
            if (_setup_field(i, dstdescr, dummy, &offset)) {
                return -1;
            }
            PyArray_DESCR(dummy)->f->copyswap(dstdata + offset,
                                              srcdata + offset, 0, dummy);
        }
        return 0;
    }

    /* Slow path */
    ret = PyArray_CastRawArrays(1, srcdata, dstdata, 0, 0,
                                srcdescr, dstdescr, 0);
    if (ret != NPY_SUCCEED) {
        return -1;
    }
    return 0;
}

static int
VOID_setitem(PyObject *op, void *input, void *vap)
{
    char *ip = input;
    PyArrayObject *ap = vap;
    PyArray_Descr *descr;
    int flags;
    int itemsize=PyArray_DESCR(ap)->elsize;
    int res;

    descr = PyArray_DESCR(ap);
    flags = PyArray_FLAGS(ap);
    if (PyDataType_HASFIELDS(descr)) {
        PyObject *errmsg;
        npy_int i;
        npy_intp offset;
        int failed = 0;

        /* If op is 0d-ndarray or numpy scalar, directly get dtype & data ptr */
        if (PyArray_Check(op)) {
            PyArrayObject *oparr = (PyArrayObject *)op;
            if (PyArray_SIZE(oparr) != 1) {
                PyErr_SetString(PyExc_ValueError,
                        "setting an array element with a sequence.");
                return -1;
            }
            return _copy_and_return_void_setitem(descr, ip,
                                    PyArray_DESCR(oparr), PyArray_DATA(oparr));
        }
        else if (PyArray_IsScalar(op, Void)) {
            PyArray_Descr *srcdescr = ((PyVoidScalarObject *)op)->descr;
            char *srcdata = ((PyVoidScalarObject *)op)->obval;
            return _copy_and_return_void_setitem(descr, ip, srcdescr, srcdata);
        }
        else if (PyTuple_Check(op)) {
            /* if it's a tuple, copy field-by-field to ap, */
            npy_intp names_size = PyTuple_GET_SIZE(descr->names);

            if (names_size != PyTuple_Size(op)) {
                errmsg = PyUString_FromFormat(
                        "could not assign tuple of length %zd to structure "
                        "with %" NPY_INTP_FMT " fields.", 
                        PyTuple_Size(op), names_size);
                PyErr_SetObject(PyExc_ValueError, errmsg);
                Py_DECREF(errmsg);
                return -1;
            }

            for (i = 0; i < names_size; i++) {
                PyObject *item;

                /* temporarily make ap have only this field */
                if (_setup_field(i, descr, ap, &offset) == -1) {
                    failed = 1;
                    break;
                }
                item = PyTuple_GetItem(op, i);
                if (item == NULL) {
                    failed = 1;
                    break;
                }
                /* use setitem to set this field */
                if (PyArray_SETITEM(ap, ip + offset, item) < 0) {
                    failed = 1;
                    break;
                }
            }
        }
        else {
            /* Otherwise must be non-void scalar. Try to assign to each field */
            npy_intp names_size = PyTuple_GET_SIZE(descr->names);

            for (i = 0; i < names_size; i++) {
                /* temporarily make ap have only this field */
                if (_setup_field(i, descr, ap, &offset) == -1) {
                    failed = 1;
                    break;
                }
                /* use setitem to set this field */
                if (PyArray_SETITEM(ap, ip + offset, op) < 0) {
                    failed = 1;
                    break;
                }
            }
        }

        /* reset clobbered attributes */
        ((PyArrayObject_fields *)(ap))->descr = descr;
        ((PyArrayObject_fields *)(ap))->flags = flags;

        if (failed) {
            return -1;
        }
        return 0;
    }
    else if (PyDataType_HASSUBARRAY(descr)) {
        /* copy into an array of the same basic type */
        PyArray_Dims shape = {NULL, -1};
        PyArrayObject *ret;
        if (!(PyArray_IntpConverter(descr->subarray->shape, &shape))) {
            npy_free_cache_dim_obj(shape);
            PyErr_SetString(PyExc_ValueError,
                    "invalid shape in fixed-type tuple.");
            return -1;
        }
        Py_INCREF(descr->subarray->base);
        ret = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type,
                        descr->subarray->base, shape.len, shape.ptr,
                        NULL, ip, PyArray_FLAGS(ap), NULL);
        npy_free_cache_dim_obj(shape);
        if (!ret) {
            return -1;
        }
        Py_INCREF(ap);
        if (PyArray_SetBaseObject(ret, (PyObject *)ap) < 0) {
            Py_DECREF(ret);
            return -1;
        }
        PyArray_UpdateFlags(ret, NPY_ARRAY_UPDATE_ALL);
        res = PyArray_CopyObject(ret, op);
        Py_DECREF(ret);
        return res;
    }

    /*
     * Fall through case - non-structured void datatype. This is a very
     * undiscerning case: It interprets any object as a buffer
     * and reads as many bytes as possible, padding with 0.
     */
    {
        const void *buffer;
        Py_ssize_t buflen;
        res = PyObject_AsReadBuffer(op, &buffer, &buflen);
        if (res == -1) {
            return -1;
        }
        memcpy(ip, buffer, PyArray_MIN(buflen, itemsize));
        if (itemsize > buflen) {
            memset(ip + buflen, 0, itemsize - buflen);
        }
    }
    return 0;
}

static PyObject *
DATETIME_getitem(void *ip, void *vap)
{
    PyArrayObject *ap = vap;
    npy_datetime dt;
    PyArray_DatetimeMetaData *meta = NULL;

    /* Get the datetime units metadata */
    meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap));
    if (meta == NULL) {
        return NULL;
    }

    if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) {
        dt = *((npy_datetime *)ip);
    }
    else {
        PyArray_DESCR(ap)->f->copyswap(&dt, ip, PyArray_ISBYTESWAPPED(ap), ap);
    }

    return convert_datetime_to_pyobject(dt, meta);
}


static PyObject *
TIMEDELTA_getitem(void *ip, void *vap)
{
    PyArrayObject *ap = vap;
    npy_timedelta td;
    PyArray_DatetimeMetaData *meta = NULL;

    /* Get the datetime units metadata */
    meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap));
    if (meta == NULL) {
        return NULL;
    }

    if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) {
        td = *((npy_timedelta *)ip);
    }
    else {
        PyArray_DESCR(ap)->f->copyswap(&td, ip, PyArray_ISBYTESWAPPED(ap), ap);
    }

    return convert_timedelta_to_pyobject(td, meta);
}

static int
DATETIME_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    /* ensure alignment */
    npy_datetime temp = 0;
    PyArray_DatetimeMetaData *meta = NULL;

    /* Get the datetime units metadata */
    meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap));
    if (meta == NULL) {
        return -1;
    }

    /* Convert the object into a NumPy datetime */
    if (convert_pyobject_to_datetime(meta, op,
                            NPY_SAME_KIND_CASTING, &temp) < 0) {
        return -1;
    }

    /* Copy the value into the output */
    if (ap == NULL || PyArray_ISBEHAVED(ap)) {
        *((npy_datetime *)ov)=temp;
    }
    else {
        PyArray_DESCR(ap)->f->copyswap(ov, &temp, PyArray_ISBYTESWAPPED(ap),
                                       ap);
    }

    return 0;
}

static int
TIMEDELTA_setitem(PyObject *op, void *ov, void *vap)
{
    PyArrayObject *ap = vap;
    /* ensure alignment */
    npy_timedelta temp = 0;
    PyArray_DatetimeMetaData *meta = NULL;

    /* Get the datetime units metadata */
    meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap));
    if (meta == NULL) {
        return -1;
    }

    /* Convert the object into a NumPy datetime */
    if (convert_pyobject_to_timedelta(meta, op,
                            NPY_SAME_KIND_CASTING, &temp) < 0) {
        return -1;
    }

    /* Copy the value into the output */
    if (ap == NULL || PyArray_ISBEHAVED(ap)) {
        *((npy_timedelta *)ov)=temp;
    }
    else {
        PyArray_DESCR(ap)->f->copyswap(ov, &temp, PyArray_ISBYTESWAPPED(ap),
                                       ap);
    }

    return 0;
}


/*
 *****************************************************************************
 **                       TYPE TO TYPE CONVERSIONS                          **
 *****************************************************************************
 */


/* Assumes contiguous, and aligned, from and to */


/**begin repeat
 *
 * #TOTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG,
 *           LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME,
 *           TIMEDELTA#
 * #totype = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *           npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *           npy_float, npy_double, npy_longdouble,
 *           npy_datetime, npy_timedelta#
 */

/**begin repeat1
 *
 * #FROMTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG,
 *             LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME,
 *             TIMEDELTA#
 * #fromtype = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_float, npy_double, npy_longdouble,
 *             npy_datetime, npy_timedelta#
 */
static void
@FROMTYPE@_to_@TOTYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @fromtype@ *ip = input;
    @totype@ *op = output;

    while (n--) {
        *op++ = (@totype@)*ip++;
    }
}
/**end repeat1**/

/**begin repeat1
 *
 * #FROMTYPE = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #fromtype = npy_float, npy_double, npy_longdouble#
 */
static void
@FROMTYPE@_to_@TOTYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @fromtype@ *ip = input;
    @totype@ *op = output;

    while (n--) {
        *op++ = (@totype@)*ip;
        ip += 2;
    }
}
/**end repeat1**/

/**end repeat**/


/**begin repeat
 *
 * #TYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG,
 *         LONGLONG, ULONGLONG, LONGDOUBLE, DATETIME,
 *         TIMEDELTA#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_longdouble,
 *         npy_datetime, npy_timedelta#
 */

static void
@TYPE@_to_HALF(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @type@ *ip = input;
    npy_half *op = output;

    while (n--) {
        *op++ = npy_float_to_half((float)(*ip++));
    }
}

static void
HALF_to_@TYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const npy_half *ip = input;
    @type@ *op = output;

    while (n--) {
        *op++ = (@type@)npy_half_to_float(*ip++);
    }
}

/**end repeat**/
#if NPY_SIZEOF_SHORT == 2
#define HALF_to_HALF SHORT_to_SHORT
#elif NPY_SIZEOF_INT == 2
#define HALF_to_HALF INT_to_INT
#endif

/**begin repeat
 *
 * #TYPE = FLOAT, DOUBLE, CFLOAT, CDOUBLE#
 * #name = float, double, float, double#
 * #itype = npy_uint32, npy_uint64, npy_uint32, npy_uint64#
 * #iscomplex = 0, 0, 1, 1#
 */

static void
@TYPE@_to_HALF(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @itype@ *ip = input;
    npy_half *op = output;

    while (n--) {
        *op++ = npy_@name@bits_to_halfbits(*ip);
#if @iscomplex@
        ip += 2;
#else
        ip++;
#endif
    }
}

static void
HALF_to_@TYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const npy_half *ip = input;
    @itype@ *op = output;

    while (n--) {
        *op++ = npy_halfbits_to_@name@bits(*ip++);
#if @iscomplex@
        *op++ = 0;
#endif
    }
}

/**end repeat**/

static void
CLONGDOUBLE_to_HALF(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const npy_longdouble *ip = input;
    npy_half *op = output;

    while (n--) {
        *op++ = npy_double_to_half((double) (*ip++));
        ip += 2;
    }
}

static void
HALF_to_CLONGDOUBLE(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const npy_half *ip = input;
    npy_longdouble *op = output;

    while (n--) {
        *op++ = npy_half_to_double(*ip++);
        *op++ = 0;
    }
}

/**begin repeat
 *
 * #FROMTYPE = BOOL,
 *             BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *             LONG, ULONG, LONGLONG, ULONGLONG,
 *             FLOAT, DOUBLE, LONGDOUBLE,
 *             DATETIME, TIMEDELTA#
 * #fromtype = npy_bool,
 *             npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_float, npy_double, npy_longdouble,
 *             npy_datetime, npy_timedelta#
 */
static void
@FROMTYPE@_to_BOOL(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @fromtype@ *ip = input;
    npy_bool *op = output;

    while (n--) {
        *op++ = (npy_bool)(*ip++ != NPY_FALSE);
    }
}
/**end repeat**/

static void
HALF_to_BOOL(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const npy_half *ip = input;
    npy_bool *op = output;

    while (n--) {
        *op++ = (npy_bool)(!npy_half_iszero(*ip++));
    }
}

/**begin repeat
 *
 * #FROMTYPE = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #fromtype = npy_cfloat, npy_cdouble, npy_clongdouble#
 */
static void
@FROMTYPE@_to_BOOL(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @fromtype@ *ip = input;
    npy_bool *op = output;

    while (n--) {
        *op = (npy_bool)((ip->real != NPY_FALSE) ||
                (ip->imag != NPY_FALSE));
        op++;
        ip++;
    }
}
/**end repeat**/

/**begin repeat
 * #TOTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *           LONG, ULONG, LONGLONG, ULONGLONG,
 *           HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *           DATETIME, TIMEDELTA#
 * #totype = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *           npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *           npy_half, npy_float, npy_double, npy_longdouble,
 *           npy_datetime, npy_timedelta#
 * #one = 1*10, NPY_HALF_ONE, 1*5#
 * #zero = 0*10, NPY_HALF_ZERO, 0*5#
 */
static void
BOOL_to_@TOTYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const npy_bool *ip = input;
    @totype@ *op = output;

    while (n--) {
        *op++ = (@totype@)((*ip++ != NPY_FALSE) ? @one@ : @zero@);
    }
}
/**end repeat**/

/**begin repeat
 *
 * #TOTYPE = CFLOAT, CDOUBLE,CLONGDOUBLE#
 * #totype = npy_float, npy_double, npy_longdouble#
 */

/**begin repeat1
 * #FROMTYPE = BOOL,
 *             BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *             LONG, ULONG, LONGLONG, ULONGLONG,
 *             FLOAT, DOUBLE, LONGDOUBLE,
 *             DATETIME, TIMEDELTA#
 * #fromtype = npy_bool,
 *             npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_float, npy_double, npy_longdouble,
 *             npy_datetime, npy_timedelta#
 */
static void
@FROMTYPE@_to_@TOTYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @fromtype@ *ip = input;
    @totype@ *op = output;

    while (n--) {
        *op++ = (@totype@)*ip++;
        *op++ = 0.0;
    }

}
/**end repeat1**/
/**end repeat**/

/**begin repeat
 *
 * #TOTYPE = CFLOAT,CDOUBLE,CLONGDOUBLE#
 * #totype = npy_float, npy_double, npy_longdouble#
 */

/**begin repeat1
 * #FROMTYPE = CFLOAT,CDOUBLE,CLONGDOUBLE#
 * #fromtype = npy_float, npy_double, npy_longdouble#
 */
static void
@FROMTYPE@_to_@TOTYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *NPY_UNUSED(aop))
{
    const @fromtype@ *ip = input;
    @totype@ *op = output;

    n <<= 1;
    while (n--) {
        *op++ = (@totype@)*ip++;
    }
}

/**end repeat1**/
/**end repeat**/

/**begin repeat
 *
 * #FROMTYPE = BOOL,
 *             BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *             LONG, ULONG, LONGLONG, ULONGLONG,
 *             HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *             CFLOAT, CDOUBLE, CLONGDOUBLE,
 *             STRING, UNICODE, VOID, OBJECT,
 *             DATETIME, TIMEDELTA#
 * #fromtype = npy_bool,
 *             npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_half, npy_float, npy_double, npy_longdouble,
 *             npy_cfloat, npy_cdouble, npy_clongdouble,
 *             npy_char, npy_char, npy_char, PyObject *,
 *             npy_datetime, npy_timedelta#
 * #skip = 1*18, PyArray_DESCR(aip)->elsize*3, 1*3#
 */
static void
@FROMTYPE@_to_OBJECT(void *input, void *output, npy_intp n,
        void *vaip, void *NPY_UNUSED(aop))
{
    @fromtype@ *ip = input;
    PyObject **op = output;
    PyArrayObject *aip = vaip;

    npy_intp i;
    int skip = @skip@;
    PyObject *tmp;
    for (i = 0; i < n; i++, ip +=skip, op++) {
        tmp = *op;
        *op = @FROMTYPE@_getitem(ip, aip);
        Py_XDECREF(tmp);
    }
}
/**end repeat**/

#define _NPY_UNUSEDBOOL  NPY_UNUSED
#define _NPY_UNUSEDBYTE  NPY_UNUSED
#define _NPY_UNUSEDUBYTE  NPY_UNUSED
#define _NPY_UNUSEDSHORT  NPY_UNUSED
#define _NPY_UNUSEDUSHORT  NPY_UNUSED
#define _NPY_UNUSEDINT  NPY_UNUSED
#define _NPY_UNUSEDUINT  NPY_UNUSED
#define _NPY_UNUSEDLONG  NPY_UNUSED
#define _NPY_UNUSEDULONG  NPY_UNUSED
#define _NPY_UNUSEDLONGLONG  NPY_UNUSED
#define _NPY_UNUSEDULONGLONG  NPY_UNUSED
#define _NPY_UNUSEDHALF NPY_UNUSED
#define _NPY_UNUSEDFLOAT  NPY_UNUSED
#define _NPY_UNUSEDDOUBLE  NPY_UNUSED
#define _NPY_UNUSEDLONGDOUBLE  NPY_UNUSED
#define _NPY_UNUSEDCFLOAT  NPY_UNUSED
#define _NPY_UNUSEDCDOUBLE  NPY_UNUSED
#define _NPY_UNUSEDCLONGDOUBLE  NPY_UNUSED
#define _NPY_UNUSEDDATETIME  NPY_UNUSED
#define _NPY_UNUSEDTIMEDELTA  NPY_UNUSED
#define _NPY_UNUSEDHALF NPY_UNUSED
#define _NPY_UNUSEDSTRING
#define _NPY_UNUSEDVOID
#define _NPY_UNUSEDUNICODE

/**begin repeat
 *
 * #TOTYPE = BOOL,
 *           BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *           LONG, ULONG, LONGLONG, ULONGLONG,
 *           HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *           CFLOAT, CDOUBLE, CLONGDOUBLE,
 *           STRING, UNICODE, VOID,
 *           DATETIME, TIMEDELTA#
 * #totype = npy_bool,
 *           npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *           npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *           npy_half, npy_float, npy_double, npy_longdouble,
 *           npy_cfloat, npy_cdouble, npy_clongdouble,
 *           npy_char, npy_char, npy_char,
 *           npy_datetime, npy_timedelta#
 * #skip = 1*18, PyArray_DESCR(aop)->elsize*3, 1*2#
 */
static void
OBJECT_to_@TOTYPE@(void *input, void *output, npy_intp n,
        void *NPY_UNUSED(aip), void *aop)
{
    PyObject **ip = input;
    @totype@ *op = output;

    npy_intp i;
    int skip = @skip@;

    for (i = 0; i < n; i++, ip++, op += skip) {
        if (*ip == NULL) {
            @TOTYPE@_setitem(Py_False, op, aop);
        }
        else {
            @TOTYPE@_setitem(*ip, op, aop);
        }
    }
}
/**end repeat**/


/**begin repeat
 *
 * #from = STRING*23, UNICODE*23, VOID*23#
 * #fromtyp = npy_char*69#
 * #to = (BOOL,
 *           BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *           LONG, ULONG, LONGLONG, ULONGLONG,
 *           HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *           CFLOAT, CDOUBLE, CLONGDOUBLE,
 *           STRING, UNICODE, VOID,
 *           DATETIME, TIMEDELTA)*3#
 * #totyp = (npy_bool,
 *              npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *              npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *              npy_half, npy_float, npy_double, npy_longdouble,
 *              npy_cfloat, npy_cdouble, npy_clongdouble,
 *              npy_char, npy_char, npy_char,
 *              npy_datetime, npy_timedelta)*3#
 * #oskip = 1*18,(PyArray_DESCR(aop)->elsize)*3,1*2,
 *          1*18,(PyArray_DESCR(aop)->elsize)*3,1*2,
 *          1*18,(PyArray_DESCR(aop)->elsize)*3,1*2#
 * #convert = 1*18, 0*3, 1*2,
 *            1*18, 0*3, 1*2,
 *            0*23#
 * #convstr = (Int*9, Long*2, Float*4, Complex*3, Tuple*3, Long*2)*3#
 */

#if @convert@

#define IS_@from@

static void
@from@_to_@to@(void *input, void *output, npy_intp n,
        void *vaip, void *aop)
{
    @fromtyp@ *ip = input;
    @totyp@ *op = output;
    PyArrayObject *aip = vaip;

    npy_intp i;
    int skip = PyArray_DESCR(aip)->elsize;
    int oskip = @oskip@;

    for (i = 0; i < n; i++, ip+=skip, op+=oskip) {
        PyObject *new;
        PyObject *temp = PyArray_Scalar(ip, PyArray_DESCR(aip), (PyObject *)aip);
        if (temp == NULL) {
            return;
        }

#if defined(NPY_PY3K) && defined(IS_STRING)
        /* Work around some Python 3K */
        new = PyUnicode_FromEncodedObject(temp, "ascii", "strict");
        Py_DECREF(temp);
        temp = new;
        if (temp == NULL) {
            return;
        }
#endif
        /* convert from Python object to needed one */
        {
            PyObject *args;

            /* call out to the Python builtin given by convstr */
            args = Py_BuildValue("(N)", temp);
#if defined(NPY_PY3K)
#define PyInt_Type PyLong_Type
#endif
            new = Py@convstr@_Type.tp_new(&Py@convstr@_Type, args, NULL);
#if defined(NPY_PY3K)
#undef PyInt_Type
#endif
            Py_DECREF(args);
            temp = new;
            if (temp == NULL) {
                return;
            }
        }

        if (@to@_setitem(temp, op, aop)) {
            Py_DECREF(temp);
            return;
        }
        Py_DECREF(temp);
    }
}

#undef IS_@from@

#else

static void
@from@_to_@to@(void *input, void *output, npy_intp n,
        void *vaip, void *aop)
{
    @fromtyp@ *ip = input;
    @totyp@ *op = output;
    PyArrayObject *aip = vaip;

    npy_intp i;
    int skip = PyArray_DESCR(aip)->elsize;
    int oskip = @oskip@;

    for (i = 0; i < n; i++, ip+=skip, op+=oskip) {
        PyObject *temp = PyArray_Scalar(ip, PyArray_DESCR(aip), (PyObject *)aip);
        if (temp == NULL) {
            return;
        }
        if (@to@_setitem(temp, op, aop)) {
            Py_DECREF(temp);
            return;
        }
        Py_DECREF(temp);
    }
}

#endif

/**end repeat**/


/**begin repeat
 *
 * #to = STRING*20, UNICODE*20, VOID*20#
 * #totyp = npy_char*20, npy_char*20, npy_char*20#
 * #from = (BOOL,
 *             BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *             LONG, ULONG, LONGLONG, ULONGLONG,
 *             HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *             CFLOAT, CDOUBLE, CLONGDOUBLE,
 *             DATETIME, TIMEDELTA)*3#
 * #fromtyp = (npy_bool,
 *               npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *               npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *               npy_half, npy_float, npy_double, npy_longdouble,
 *               npy_cfloat, npy_cdouble, npy_clongdouble,
 *               npy_datetime, npy_timedelta)*3#
 */
static void
@from@_to_@to@(void *input, void *output, npy_intp n,
        void *vaip, void *vaop)
{
    @fromtyp@ *ip = input;
    @totyp@ *op = output;
    PyArrayObject *aip = vaip;
    PyArrayObject *aop = vaop;

    npy_intp i;
    PyObject *temp = NULL;
    int skip = 1;
    int oskip = PyArray_DESCR(aop)->elsize;
    for (i = 0; i < n; i++, ip += skip, op += oskip) {
        temp = PyArray_Scalar(ip, PyArray_DESCR(aip), (PyObject *)aip);
        if (temp == NULL) {
            Py_INCREF(Py_False);
            temp = Py_False;
        }
        if (@to@_setitem(temp, op, aop)) {
            Py_DECREF(temp);
            return;
        }
        Py_DECREF(temp);
    }
}

/**end repeat**/


/*
 *****************************************************************************
 **                               SCAN                                      **
 *****************************************************************************
 */


/*
 * The first ignore argument is for backwards compatibility.
 * Should be removed when the API version is bumped up.
 */

/**begin repeat
 * #fname = SHORT, USHORT, INT, UINT,
 *          LONG, ULONG, LONGLONG, ULONGLONG#
 * #type = npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 * #format = "hd", "hu", "d", "u",
 *           "ld", "lu", NPY_LONGLONG_FMT, NPY_ULONGLONG_FMT#
 */
static int
@fname@_scan(FILE *fp, @type@ *ip, void *NPY_UNUSED(ignore),
        PyArray_Descr *NPY_UNUSED(ignored))
{
    return fscanf(fp, "%"@format@, ip);
}
/**end repeat**/

/**begin repeat
 * #fname = FLOAT, DOUBLE#
 * #type = npy_float, npy_double#
 */
static int
@fname@_scan(FILE *fp, @type@ *ip, void *NPY_UNUSED(ignore),
        PyArray_Descr *NPY_UNUSED(ignored))
{
    double result;
    int ret;

    ret = NumPyOS_ascii_ftolf(fp, &result);
    *ip = (@type@) result;
    return ret;
}
/**end repeat**/

static int
LONGDOUBLE_scan(FILE *fp, npy_longdouble *ip, void *NPY_UNUSED(ignore),
        PyArray_Descr *NPY_UNUSED(ignored))
{
    long double result;
    int ret;

    ret = NumPyOS_ascii_ftoLf(fp, &result);
    *ip = (npy_longdouble) result;
    return ret;
}

static int
HALF_scan(FILE *fp, npy_half *ip, void *NPY_UNUSED(ignore),
        PyArray_Descr *NPY_UNUSED(ignored))
{
    double result;
    int ret;

    ret = NumPyOS_ascii_ftolf(fp, &result);
    *ip = npy_double_to_half(result);
    return ret;
}

/**begin repeat
 * #fname = BYTE, UBYTE#
 * #type = npy_byte, npy_ubyte#
 * #btype = npy_int, npy_uint#
 * #format = "d", "u"#
 */
static int
@fname@_scan(FILE *fp, @type@ *ip, void *NPY_UNUSED(ignore),
        PyArray_Descr *NPY_UNUSED(ignore2))
{
    @btype@ temp;
    int num;

    num = fscanf(fp, "%"@format@, &temp);
    *ip = (@type@) temp;
    return num;
}
/**end repeat**/

static int
BOOL_scan(FILE *fp, npy_bool *ip, void *NPY_UNUSED(ignore),
        PyArray_Descr *NPY_UNUSED(ignore2))
{
    double result;
    int ret;

    ret = NumPyOS_ascii_ftolf(fp, &result);
    *ip = (npy_bool) (result != 0.0);
    return ret;
}

/**begin repeat
 * #fname = CFLOAT, CDOUBLE, CLONGDOUBLE,
 *          OBJECT, STRING, UNICODE, VOID,
 *          DATETIME, TIMEDELTA#
 */

#define @fname@_scan NULL

/**end repeat**/


/*
 *****************************************************************************
 **                             FROMSTR                                     **
 *****************************************************************************
 */


/**begin repeat
 * #fname = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *          LONG, ULONG, LONGLONG, ULONGLONG,
 *          DATETIME, TIMEDELTA#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_datetime, npy_timedelta#
 * #func = (PyOS_strtol, PyOS_strtoul)*4, npy_strtoll, npy_strtoull,
 *         npy_strtoll*2#
 * #btype = (npy_long, npy_ulong)*4, npy_longlong, npy_ulonglong,
 *          npy_longlong*2#
 */
static int
@fname@_fromstr(char *str, void *ip, char **endptr,
        PyArray_Descr *NPY_UNUSED(ignore))
{
    @btype@ result;

    result = @func@(str, endptr, 10);
    *(@type@ *)ip = result;
    return 0;
}
/**end repeat**/

/**begin repeat
 *
 * #fname = FLOAT, DOUBLE#
 * #type = npy_float, npy_double#
 */
static int
@fname@_fromstr(char *str, void *ip, char **endptr,
        PyArray_Descr *NPY_UNUSED(ignore))
{
    double result;

    result = NumPyOS_ascii_strtod(str, endptr);
    *(@type@ *)ip = result;
    return 0;
}
/**end repeat**/

static int
LONGDOUBLE_fromstr(char *str, void *ip, char **endptr,
        PyArray_Descr *NPY_UNUSED(ignore))
{
    long double result;

    result = NumPyOS_ascii_strtold(str, endptr);
    *(npy_longdouble *)ip = result;
    return 0;
}

static int
HALF_fromstr(char *str, void *ip, char **endptr,
        PyArray_Descr *NPY_UNUSED(ignore))
{
    double result;

    result = NumPyOS_ascii_strtod(str, endptr);
    *(npy_half *)ip = npy_double_to_half(result);
    return 0;
}

static int
BOOL_fromstr(char *str, void *ip, char **endptr,
        PyArray_Descr *NPY_UNUSED(ignore))
{
    double result;

    result = NumPyOS_ascii_strtod(str, endptr);
    *(npy_bool *)ip = (result != 0.0);
    return 0;
}

/**begin repeat
 * #fname = CFLOAT, CDOUBLE, CLONGDOUBLE,
 *          OBJECT, STRING, UNICODE, VOID#
 */

#define @fname@_fromstr NULL

/**end repeat**/


/*
 *****************************************************************************
 **                            COPYSWAPN                                    **
 *****************************************************************************
 */


static NPY_INLINE void
_basic_copyn(void *dst, npy_intp dstride, void *src, npy_intp sstride,
             npy_intp n, int elsize) {
    if (src == NULL) {
        return;
    }
    if (sstride == elsize && dstride == elsize) {
        memcpy(dst, src, n*elsize);
    }
    else {
        _unaligned_strided_byte_copy(dst, dstride, src, sstride,
                n, elsize);
    }
}

static NPY_INLINE void
_basic_copy(void *dst, void *src, int elsize) {
    if (src == NULL) {
        return;
    }
    memcpy(dst, src, elsize);
}


/**begin repeat
 *
 * #fname = SHORT, USHORT, INT, UINT,
 *          LONG, ULONG, LONGLONG, ULONGLONG,
 *          HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *          DATETIME, TIMEDELTA#
 * #fsize = SHORT, SHORT, INT, INT,
 *          LONG, LONG, LONGLONG, LONGLONG,
 *          HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *          DATETIME, TIMEDELTA#
 * #type = npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_datetime, npy_timedelta#
 */
static void
@fname@_copyswapn (void *dst, npy_intp dstride, void *src, npy_intp sstride,
                   npy_intp n, int swap, void *NPY_UNUSED(arr))
{
    /* copy first if needed */
    _basic_copyn(dst, dstride, src, sstride, n, sizeof(@type@));
    if (swap) {
        _strided_byte_swap(dst, dstride, n, sizeof(@type@));
    }
}

static void
@fname@_copyswap (void *dst, void *src, int swap, void *NPY_UNUSED(arr))
{
    /* copy first if needed */
    _basic_copy(dst, src, sizeof(@type@));

    if (swap) {
        char *a, *b, c;

        a = (char *)dst;
#if NPY_SIZEOF_@fsize@ == 2
        b = a + 1;
        c = *a; *a++ = *b; *b = c;
#elif NPY_SIZEOF_@fsize@ == 4
        b = a + 3;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 8
        b = a + 7;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 10
        b = a + 9;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 12
        b = a + 11;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 16
        b = a + 15;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#else
        {
            int i, nn;

            b = a + (NPY_SIZEOF_@fsize@-1);
            nn = NPY_SIZEOF_@fsize@ / 2;
            for (i = 0; i < nn; i++) {
                c = *a;
                *a++ = *b;
                *b-- = c;
            }
        }
#endif
    }
}

/**end repeat**/

/**begin repeat
 *
 * #fname = BOOL,
 *          BYTE, UBYTE#
 * #type = npy_bool,
 *         npy_byte, npy_ubyte#
 */
static void
@fname@_copyswapn (void *dst, npy_intp dstride, void *src, npy_intp sstride,
        npy_intp n, int NPY_UNUSED(swap), void *NPY_UNUSED(arr))
{
    /* copy first if needed */
    _basic_copyn(dst, dstride, src, sstride, n, sizeof(@type@));
    /* ignore swap */
}

static void
@fname@_copyswap (void *dst, void *src, int NPY_UNUSED(swap),
        void *NPY_UNUSED(arr))
{
    /* copy first if needed */
    _basic_copy(dst, src, sizeof(@type@));
    /* ignore swap */
}

/**end repeat**/



/**begin repeat
 *
 * #fname = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #fsize = FLOAT, DOUBLE, LONGDOUBLE#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
*/
static void
@fname@_copyswapn (void *dst, npy_intp dstride, void *src, npy_intp sstride,
        npy_intp n, int swap, void *NPY_UNUSED(arr))
{
    /* copy first if needed */
    _basic_copyn(dst, dstride, src, sstride, n, sizeof(@type@));

    if (swap) {
        _strided_byte_swap(dst, dstride, n, NPY_SIZEOF_@fsize@);
        _strided_byte_swap(((char *)dst + NPY_SIZEOF_@fsize@), dstride,
                n, NPY_SIZEOF_@fsize@);
    }
}

static void
@fname@_copyswap (void *dst, void *src, int swap, void *NPY_UNUSED(arr))
{
    /* copy first if needed */
    _basic_copy(dst, src, sizeof(@type@));

    if (swap) {
        char *a, *b, c;
        a = (char *)dst;
#if NPY_SIZEOF_@fsize@ == 4
        b = a + 3;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
        a += 2;
        b = a + 3;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 8
        b = a + 7;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
        a += 4;
        b = a + 7;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 10
        b = a + 9;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
        a += 5;
        b = a + 9;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 12
        b = a + 11;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
        a += 6;
        b = a + 11;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#elif NPY_SIZEOF_@fsize@ == 16
        b = a + 15;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
        a += 8;
        b = a + 15;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b-- = c;
        c = *a; *a++ = *b; *b   = c;
#else
        {
            int i, nn;

            b = a + (NPY_SIZEOF_@fsize@ - 1);
            nn = NPY_SIZEOF_@fsize@ / 2;
            for (i = 0; i < nn; i++) {
                c = *a;
                *a++ = *b;
                *b-- = c;
            }
            a += nn;
            b = a + (NPY_SIZEOF_@fsize@ - 1);
            for (i = 0; i < nn; i++) {
                c = *a;
                *a++ = *b;
                *b-- = c;
            }
        }
#endif
    }
}

/**end repeat**/

static void
OBJECT_copyswapn(PyObject **dst, npy_intp dstride, PyObject **src,
        npy_intp sstride, npy_intp n, int NPY_UNUSED(swap),
        void *NPY_UNUSED(arr))
{
    npy_intp i;
    if (src != NULL) {
        if (__ALIGNED(dst, sizeof(PyObject **))
                && __ALIGNED(src, sizeof(PyObject **))
                && __ALIGNED(dstride, sizeof(PyObject **))
                && __ALIGNED(sstride, sizeof(PyObject **))) {
            dstride /= sizeof(PyObject **);
            sstride /= sizeof(PyObject **);
            for (i = 0; i < n; i++) {
                Py_XINCREF(*src);
                Py_XDECREF(*dst);
                *dst = *src;
                dst += dstride;
                src += sstride;
            }
        }
        else {
            unsigned char *dstp, *srcp;
            PyObject *tmp;
            dstp = (unsigned char*)dst;
            srcp = (unsigned char*)src;
            for (i = 0; i < n; i++) {
                NPY_COPY_PYOBJECT_PTR(&tmp, srcp);
                Py_XINCREF(tmp);
                NPY_COPY_PYOBJECT_PTR(&tmp, dstp);
                Py_XDECREF(tmp);
                NPY_COPY_PYOBJECT_PTR(dstp, srcp);
                dstp += dstride;
                srcp += sstride;
            }
        }
    }
    /* ignore swap */
    return;
}

static void
OBJECT_copyswap(PyObject **dst, PyObject **src, int NPY_UNUSED(swap),
        void *NPY_UNUSED(arr))
{

    if (src != NULL) {
        if (__ALIGNED(dst,sizeof(PyObject **)) &&
                __ALIGNED(src,sizeof(PyObject **))) {
            Py_XINCREF(*src);
            Py_XDECREF(*dst);
            *dst = *src;
        }
        else {
            PyObject *tmp;
            NPY_COPY_PYOBJECT_PTR(&tmp, src);
            Py_XINCREF(tmp);
            NPY_COPY_PYOBJECT_PTR(&tmp, dst);
            Py_XDECREF(tmp);
            NPY_COPY_PYOBJECT_PTR(dst, src);
        }
    }
}

/* ignore swap */
static void
STRING_copyswapn (char *dst, npy_intp dstride, char *src, npy_intp sstride,
                  npy_intp n, int NPY_UNUSED(swap), PyArrayObject *arr)
{
    if (arr == NULL) {
        return;
    }
    _basic_copyn(dst, dstride, src, sstride, n, PyArray_DESCR(arr)->elsize);
    return;
}

/* */
static void
VOID_copyswapn (char *dst, npy_intp dstride, char *src, npy_intp sstride,
                npy_intp n, int swap, PyArrayObject *arr)
{
    if (arr == NULL) {
        return;
    }
    if (PyArray_HASFIELDS(arr)) {
        PyObject *key, *value;
        PyArray_Descr *descr;
        Py_ssize_t pos = 0;

        descr = PyArray_DESCR(arr);
        while (PyDict_Next(descr->fields, &pos, &key, &value)) {
            npy_intp offset;
            PyArray_Descr * new;
            if (NPY_TITLE_KEY(key, value)) {
                continue;
            }
            if (_unpack_field(value, &new, &offset) < 0) {
                ((PyArrayObject_fields *)arr)->descr = descr;
                return;
            }
            /*
             * TODO: temporarily modifying the array like this
             *       is bad coding style, should be changed.
             */
            ((PyArrayObject_fields *)arr)->descr = new;
            new->f->copyswapn(dst+offset, dstride,
                    (src != NULL ? src+offset : NULL),
                    sstride, n, swap, arr);
        }
        ((PyArrayObject_fields *)arr)->descr = descr;
        return;
    }
    if (swap && PyArray_DESCR(arr)->subarray != NULL) {
        PyArray_Descr *descr, *new;
        npy_intp num;
        npy_intp i;
        int subitemsize;
        char *dstptr, *srcptr;

        descr = PyArray_DESCR(arr);
        new = descr->subarray->base;
        /*
         * TODO: temporarily modifying the array like this
         *       is bad coding style, should be changed.
         */
        ((PyArrayObject_fields *)arr)->descr = new;
        dstptr = dst;
        srcptr = src;
        subitemsize = new->elsize;
        num = descr->elsize / subitemsize;
        for (i = 0; i < n; i++) {
            new->f->copyswapn(dstptr, subitemsize, srcptr,
                    subitemsize, num, swap, arr);
            dstptr += dstride;
            if (srcptr) {
                srcptr += sstride;
            }
        }
        ((PyArrayObject_fields *)arr)->descr = descr;
        return;
    }
    _basic_copyn(dst, dstride, src, sstride, n, PyArray_DESCR(arr)->elsize);
    return;
}

static void
VOID_copyswap (char *dst, char *src, int swap, PyArrayObject *arr)
{
    if (arr == NULL) {
        return;
    }
    if (PyArray_HASFIELDS(arr)) {
        PyObject *key, *value;
        PyArray_Descr *descr;
        Py_ssize_t pos = 0;

        descr = PyArray_DESCR(arr);
        while (PyDict_Next(descr->fields, &pos, &key, &value)) {
            npy_intp offset;
            PyArray_Descr * new;
            if (NPY_TITLE_KEY(key, value)) {
                continue;
            }
            if (_unpack_field(value, &new, &offset) < 0) {
                ((PyArrayObject_fields *)arr)->descr = descr;
                return;
            }
            /*
             * TODO: temporarily modifying the array like this
             *       is bad coding style, should be changed.
             */
            ((PyArrayObject_fields *)arr)->descr = new;
            new->f->copyswap(dst+offset,
                    (src != NULL ? src+offset : NULL),
                    swap, arr);
        }
        ((PyArrayObject_fields *)arr)->descr = descr;
        return;
    }
    if (swap && PyArray_DESCR(arr)->subarray != NULL) {
        PyArray_Descr *descr, *new;
        npy_intp num;
        int itemsize;

        descr = PyArray_DESCR(arr);
        new = descr->subarray->base;
        /*
         * TODO: temporarily modifying the array like this
         *       is bad coding style, should be changed.
         */
        ((PyArrayObject_fields *)arr)->descr = new;
        itemsize = new->elsize;
        num = descr->elsize / itemsize;
        new->f->copyswapn(dst, itemsize, src,
                itemsize, num, swap, arr);
        ((PyArrayObject_fields *)arr)->descr = descr;
        return;
    }

    /* copy first if needed */
    _basic_copy(dst, src, PyArray_DESCR(arr)->elsize);
    return;
}


static void
UNICODE_copyswapn (char *dst, npy_intp dstride, char *src, npy_intp sstride,
                   npy_intp n, int swap, PyArrayObject *arr)
{
    int itemsize;

    if (arr == NULL) {
        return;
    }
    itemsize = PyArray_DESCR(arr)->elsize;
    _basic_copyn(dst, dstride, src, sstride, n, itemsize);

    if (swap) {
        int i;
        char *_dst;
        itemsize = itemsize / 4;

        while (n > 0) {
            _dst = dst;
            for (i=0; i < itemsize; i++) {
                npy_bswap4_unaligned(_dst);
                _dst += 4;
            }
            dst += dstride;
            --n;
        }
    }
}


static void
STRING_copyswap(char *dst, char *src, int NPY_UNUSED(swap), PyArrayObject *arr)
{
    if (arr == NULL) {
        return;
    }
    /* copy first if needed */
    _basic_copy(dst, src, PyArray_DESCR(arr)->elsize);
}

static void
UNICODE_copyswap (char *dst, char *src, int swap, PyArrayObject *arr)
{
    int itemsize;

    if (arr == NULL) {
        return;
    }
    itemsize = PyArray_DESCR(arr)->elsize;
    _basic_copy(dst, src, itemsize);

    if (swap) {
        int i;
        char *_dst;
        itemsize = itemsize / 4;

        _dst = dst;
        for (i=0; i < itemsize; i++) {
            npy_bswap4_unaligned(_dst);
            _dst += 4;
        }
    }
}


/*
 *****************************************************************************
 **                                 NONZERO                                 **
 *****************************************************************************
 */

#define _NONZERO(a) ((a) != 0)

/**begin repeat
 *
 * #fname = BOOL,
 *          BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *          LONG, ULONG, LONGLONG, ULONGLONG,
 *          HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *          DATETIME, TIMEDELTA#
 * #type = npy_bool,
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_datetime, npy_timedelta#
 * #isfloat = 0*11, 1*4, 0*2#
 * #nonzero = _NONZERO*11, !npy_half_iszero, _NONZERO*5#
 */
static npy_bool
@fname@_nonzero (char *ip, PyArrayObject *ap)
{
    if (ap == NULL || PyArray_ISBEHAVED_RO(ap)) {
        @type@ *ptmp = (@type@ *)ip;
        return (npy_bool) @nonzero@(*ptmp);
    }
    else {
        /*
         * Don't worry about swapping for integer types,
         * since we are just testing for equality with 0.
         * For float types, the signed zeros require us to swap.
         */
        @type@ tmp;
#if @isfloat@
        PyArray_DESCR(ap)->f->copyswap(&tmp, ip, PyArray_ISBYTESWAPPED(ap),
                                       ap);
#else
        memcpy(&tmp, ip, sizeof(@type@));
#endif
        return (npy_bool) @nonzero@(tmp);
    }
}
/**end repeat**/

/**begin repeat
 *
 * #fname = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 */
static npy_bool
@fname@_nonzero (char *ip, PyArrayObject *ap)
{
    if (ap == NULL || PyArray_ISBEHAVED_RO(ap)) {
        @type@ *ptmp = (@type@ *)ip;
        return (npy_bool) ((ptmp->real != 0) || (ptmp->imag != 0));
    }
    else {
        @type@ tmp;
        PyArray_DESCR(ap)->f->copyswap(&tmp, ip, PyArray_ISBYTESWAPPED(ap),
                                       ap);
        return (npy_bool) ((tmp.real != 0) || (tmp.imag != 0));
    }
}
/**end repeat**/


#define WHITESPACE " \t\n\r\v\f"
#define WHITELEN 6

static npy_bool
Py_STRING_ISSPACE(char ch)
{
    char white[] = WHITESPACE;
    int j;
    npy_bool space = NPY_FALSE;

    for (j = 0; j < WHITELEN; j++) {
        if (ch == white[j]) {
            space = NPY_TRUE;
            break;
        }
    }
    return space;
}

static npy_bool
STRING_nonzero (char *ip, PyArrayObject *ap)
{
    int len = PyArray_DESCR(ap)->elsize;
    int i;
    npy_bool nonz = NPY_FALSE;
    npy_bool seen_null = NPY_FALSE;

    for (i = 0; i < len; i++) {
        if (*ip == '\0') {
            seen_null = NPY_TRUE;
        }
        else if (seen_null || !Py_STRING_ISSPACE(*ip)) {
            nonz = NPY_TRUE;
            break;
        }
        ip++;
    }
    return nonz;
}

#ifdef Py_UNICODE_WIDE
#define PyArray_UCS4_ISSPACE Py_UNICODE_ISSPACE
#else
#define PyArray_UCS4_ISSPACE(ch) Py_STRING_ISSPACE((char)ch)
#endif

static npy_bool
UNICODE_nonzero (npy_ucs4 *ip, PyArrayObject *ap)
{
    int len = PyArray_DESCR(ap)->elsize >> 2;
    int i;
    npy_bool nonz = NPY_FALSE;
    npy_bool seen_null = NPY_FALSE;
    char *buffer = NULL;

    if (PyArray_ISBYTESWAPPED(ap) || !PyArray_ISALIGNED(ap)) {
        buffer = PyArray_malloc(PyArray_DESCR(ap)->elsize);
        if (buffer == NULL) {
            return nonz;
        }
        memcpy(buffer, ip, PyArray_DESCR(ap)->elsize);
        if (PyArray_ISBYTESWAPPED(ap)) {
            byte_swap_vector(buffer, len, 4);
        }
        ip = (npy_ucs4 *)buffer;
    }

    for (i = 0; i < len; i++) {
        if (*ip == '\0') {
            seen_null = NPY_TRUE;
        }
        else if (seen_null || !PyArray_UCS4_ISSPACE(*ip)) {
            nonz = NPY_TRUE;
            break;
        }
        ip++;
    }
    PyArray_free(buffer);
    return nonz;
}

static npy_bool
OBJECT_nonzero (PyObject **ip, PyArrayObject *ap)
{

    if (PyArray_ISALIGNED(ap)) {
        if (*ip == NULL) {
            return NPY_FALSE;
        }
        return (npy_bool) PyObject_IsTrue(*ip);
    }
    else {
        PyObject *obj;
        NPY_COPY_PYOBJECT_PTR(&obj, ip);
        if (obj == NULL) {
            return NPY_FALSE;
        }
        return (npy_bool) PyObject_IsTrue(obj);
    }
}

/*
 * if we have fields, then nonzero only if all sub-fields are nonzero.
 */
static npy_bool
VOID_nonzero (char *ip, PyArrayObject *ap)
{
    int i;
    int len;
    npy_bool nonz = NPY_FALSE;

    if (PyArray_HASFIELDS(ap)) {
        PyArray_Descr *descr;
        PyObject *key, *value;
        int savedflags;
        Py_ssize_t pos = 0;

        descr = PyArray_DESCR(ap);
        savedflags = PyArray_FLAGS(ap);
        while (PyDict_Next(descr->fields, &pos, &key, &value)) {
            PyArray_Descr * new;
            npy_intp offset;
            if (NPY_TITLE_KEY(key, value)) {
                continue;
            }
            if (_unpack_field(value, &new, &offset) < 0) {
                PyErr_Clear();
                continue;
            }
            /*
             * TODO: temporarily modifying the array like this
             *       is bad coding style, should be changed.
             */
            ((PyArrayObject_fields *)ap)->descr = new;
            ((PyArrayObject_fields *)ap)->flags = savedflags;
            if ((new->alignment > 1) && !__ALIGNED(ip + offset,
                        new->alignment)) {
                PyArray_CLEARFLAGS(ap, NPY_ARRAY_ALIGNED);
            }
            else {
                PyArray_ENABLEFLAGS(ap, NPY_ARRAY_ALIGNED);
            }
            if (new->f->nonzero(ip+offset, ap)) {
                nonz = NPY_TRUE;
                break;
            }
        }
        ((PyArrayObject_fields *)ap)->descr = descr;
        ((PyArrayObject_fields *)ap)->flags = savedflags;
        return nonz;
    }
    len = PyArray_DESCR(ap)->elsize;
    for (i = 0; i < len; i++) {
        if (*ip != '\0') {
            nonz = NPY_TRUE;
            break;
        }
        ip++;
    }
    return nonz;
}

#undef __ALIGNED


/*
 *****************************************************************************
 **                                 COMPARE                                 **
 *****************************************************************************
 */


/* boolean type */

static int
BOOL_compare(npy_bool *ip1, npy_bool *ip2, PyArrayObject *NPY_UNUSED(ap))
{
    return (*ip1 ? (*ip2 ? 0 : 1) : (*ip2 ? -1 : 0));
}


/* integer types */

/**begin repeat
 * #TYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         DATETIME, TIMEDELTA#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_datetime, npy_timedelta#
 */

static int
@TYPE@_compare (@type@ *pa, @type@ *pb, PyArrayObject *NPY_UNUSED(ap))
{
    const @type@ a = *pa;
    const @type@ b = *pb;

    return a < b ? -1 : a == b ? 0 : 1;
}

/**end repeat**/


/* float types */

/*
 * The real/complex comparison functions are compatible with the new sort
 * order for nans introduced in numpy 1.4.0. All nan values now compare
 * larger than non-nan values and are sorted to the end. The comparison
 * order is:
 *
 *      Real: [R, nan]
 *      Complex: [R + Rj, R + nanj, nan + Rj, nan + nanj]
 *
 *  where complex values with the same nan placements are sorted according
 *  to the non-nan part if it exists. If both the real and imaginary parts
 *  of complex types are non-nan the order is the same as the real parts
 *  unless they happen to be equal, in which case the order is that of the
 *  imaginary parts.
 */

/**begin repeat
 *
 * #TYPE = FLOAT, DOUBLE, LONGDOUBLE#
 * #type = npy_float, npy_double, npy_longdouble#
 */

#define LT(a,b) ((a) < (b) || ((b) != (b) && (a) ==(a)))

static int
@TYPE@_compare(@type@ *pa, @type@ *pb)
{
    const @type@ a = *pa;
    const @type@ b = *pb;
    int ret;

    if (LT(a,b)) {
        ret = -1;
    }
    else if (LT(b,a)) {
        ret = 1;
    }
    else {
        ret = 0;
    }
    return ret;
}


static int
C@TYPE@_compare(@type@ *pa, @type@ *pb)
{
    const @type@ ar = pa[0];
    const @type@ ai = pa[1];
    const @type@ br = pb[0];
    const @type@ bi = pb[1];
    int ret;

    if (ar < br) {
        if (ai == ai || bi != bi) {
            ret = -1;
        }
        else {
            ret = 1;
        }
    }
    else if (br < ar) {
        if (bi == bi || ai != ai) {
            ret = 1;
        }
        else {
            ret = -1;
        }
    }
    else if (ar == br || (ar != ar && br != br)) {
        if (LT(ai,bi)) {
            ret = -1;
        }
        else if (LT(bi,ai)) {
            ret = 1;
        }
        else {
            ret = 0;
        }
    }
    else if (ar == ar) {
        ret = -1;
    }
    else {
        ret = 1;
    }

    return ret;
}

#undef LT

/**end repeat**/

static int
HALF_compare (npy_half *pa, npy_half *pb, PyArrayObject *NPY_UNUSED(ap))
{
    npy_half a = *pa, b = *pb;
    npy_bool a_isnan, b_isnan;
    int ret;

    a_isnan = npy_half_isnan(a);
    b_isnan = npy_half_isnan(b);

    if (a_isnan) {
        ret = b_isnan ? 0 : -1;
    }
    else if (b_isnan) {
        ret = 1;
    }
    else if(npy_half_lt_nonan(a, b)) {
        ret = -1;
    }
    else if(npy_half_lt_nonan(b, a)) {
        ret = 1;
    }
    else {
        ret = 0;
    }

    return ret;
}


/* object type */

static int
OBJECT_compare(PyObject **ip1, PyObject **ip2, PyArrayObject *NPY_UNUSED(ap))
{
    /*
     * ALIGNMENT NOTE: It seems that PyArray_Sort is already handling
     * the alignment of pointers, so it doesn't need to be handled
     * here.
     */

    int ret;
    /*
     * work around gh-3879, we cannot abort an in-progress quicksort
     * so at least do not raise again
     */
    if (PyErr_Occurred()) {
        return 0;
    }
    if ((*ip1 == NULL) || (*ip2 == NULL)) {
        if (ip1 == ip2) {
            return 1;
        }
        if (ip1 == NULL) {
            return -1;
        }
        return 1;
    }

    ret = PyObject_RichCompareBool(*ip1, *ip2, Py_LT);
    if (ret < 0) {
        /* error occurred, avoid the next call to PyObject_RichCompareBool */ 
        return 0;
    }
    if (ret == 1) {
        return -1;
    }
    else if (PyObject_RichCompareBool(*ip1, *ip2, Py_GT) == 1) {
        return 1;
    }
    else {
        return 0;
    }
}


/* string type */

static int
STRING_compare(char *ip1, char *ip2, PyArrayObject *ap)
{
    const unsigned char *c1 = (unsigned char *)ip1;
    const unsigned char *c2 = (unsigned char *)ip2;
    const size_t len = PyArray_DESCR(ap)->elsize;
    int i;

    i = memcmp(c1, c2, len);
    if (i > 0) {
        return 1;
    }
    else if (i < 0) {
        return -1;
    }
    return 0;
}


/* unicode type */

static int
UNICODE_compare(npy_ucs4 *ip1, npy_ucs4 *ip2,
                PyArrayObject *ap)
{
    int itemsize = PyArray_DESCR(ap)->elsize;

    if (itemsize < 0) {
        return 0;
    }
    itemsize /= sizeof(npy_ucs4);
    while (itemsize-- > 0) {
        npy_ucs4 c1 = *ip1++;
        npy_ucs4 c2 = *ip2++;
        if (c1 != c2) {
            return (c1 < c2) ? -1 : 1;
        }
    }
    return 0;
}


/* void type */

/*
 * If fields are defined, then compare on first field and if equal
 * compare on second field.  Continue until done or comparison results
 * in not_equal.
 *
 * Must align data passed on to sub-comparisons.
 * Also must swap data based on to sub-comparisons.
 */
static int
VOID_compare(char *ip1, char *ip2, PyArrayObject *ap)
{
    PyArray_Descr *descr;
    PyObject *names, *key;
    PyObject *tup;
    PyArrayObject_fields dummy_struct;
    PyArrayObject *dummy = (PyArrayObject *)&dummy_struct;
    char *nip1, *nip2;
    int i, res = 0, swap = 0;

    if (!PyArray_HASFIELDS(ap)) {
        return STRING_compare(ip1, ip2, ap);
    }
    descr = PyArray_DESCR(ap);
    /*
     * Compare on the first-field.  If equal, then
     * compare on the second-field, etc.
     */
    names = descr->names;
    for (i = 0; i < PyTuple_GET_SIZE(names); i++) {
        PyArray_Descr *new;
        npy_intp offset;
        key = PyTuple_GET_ITEM(names, i);
        tup = PyDict_GetItem(descr->fields, key);
        if (_unpack_field(tup, &new, &offset) < 0) {
            goto finish;
        }
        /* descr is the only field checked by compare or copyswap */
        dummy_struct.descr = new;
        swap = PyArray_ISBYTESWAPPED(dummy);
        nip1 = ip1 + offset;
        nip2 = ip2 + offset;
        if (swap || new->alignment > 1) {
            if (swap || !npy_is_aligned(nip1, new->alignment)) {
                /* create buffer and copy */
                nip1 = npy_alloc_cache(new->elsize);
                if (nip1 == NULL) {
                    goto finish;
                }
                memcpy(nip1, ip1 + offset, new->elsize);
                if (swap)
                    new->f->copyswap(nip1, NULL, swap, dummy);
            }
            if (swap || !npy_is_aligned(nip2, new->alignment)) {
                /* create buffer and copy */
                nip2 = npy_alloc_cache(new->elsize);
                if (nip2 == NULL) {
                    if (nip1 != ip1 + offset) {
                        npy_free_cache(nip1, new->elsize);
                    }
                    goto finish;
                }
                memcpy(nip2, ip2 + offset, new->elsize);
                if (swap)
                    new->f->copyswap(nip2, NULL, swap, dummy);
            }
        }
        res = new->f->compare(nip1, nip2, dummy);
        if (swap || new->alignment > 1) {
            if (nip1 != ip1 + offset) {
                npy_free_cache(nip1, new->elsize);
            }
            if (nip2 != ip2 + offset) {
                npy_free_cache(nip2, new->elsize);
            }
        }
        if (res != 0) {
            break;
        }
    }

finish:
    return res;
}


/*
 *****************************************************************************
 **                                 ARGFUNC                                 **
 *****************************************************************************
 */

#define _LESS_THAN_OR_EQUAL(a,b) ((a) <= (b))

static int
BOOL_argmax(npy_bool *ip, npy_intp n, npy_intp *max_ind,
            PyArrayObject *NPY_UNUSED(aip))

{
    npy_intp i = 0;
    /* memcmp like logical_and on i386 is maybe slower for small arrays */
#ifdef NPY_HAVE_SSE2_INTRINSICS
    const __m128i zero = _mm_setzero_si128();
    for (; i < n - (n % 32); i+=32) {
        __m128i d1 = _mm_loadu_si128((__m128i*)&ip[i]);
        __m128i d2 = _mm_loadu_si128((__m128i*)&ip[i + 16]);
        d1 = _mm_cmpeq_epi8(d1, zero);
        d2 = _mm_cmpeq_epi8(d2, zero);
        if (_mm_movemask_epi8(_mm_min_epu8(d1, d2)) != 0xFFFF) {
            break;
        }
    }
#endif
    for (; i < n; i++) {
        if (ip[i]) {
            *max_ind = i;
            return 0;
        }
    }
    *max_ind = 0;
    return 0;
}

/**begin repeat
 *
 * #fname = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *          LONG, ULONG, LONGLONG, ULONGLONG,
 *          HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *          CFLOAT, CDOUBLE, CLONGDOUBLE,
 *          DATETIME, TIMEDELTA#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_float, npy_double, npy_longdouble,
 *         npy_datetime, npy_timedelta#
 * #isfloat = 0*10, 1*7, 0*2#
 * #isnan = nop*10, npy_half_isnan, npy_isnan*6, nop*2#
 * #le = _LESS_THAN_OR_EQUAL*10, npy_half_le, _LESS_THAN_OR_EQUAL*8#
 * #iscomplex = 0*14, 1*3, 0*2#
 * #incr = ip++*14, ip+=2*3, ip++*2#
 */
static int
@fname@_argmax(@type@ *ip, npy_intp n, npy_intp *max_ind,
        PyArrayObject *NPY_UNUSED(aip))
{
    npy_intp i;
    @type@ mp = *ip;
#if @iscomplex@
    @type@ mp_im = ip[1];
#endif

    *max_ind = 0;

#if @isfloat@
    if (@isnan@(mp)) {
        /* nan encountered; it's maximal */
        return 0;
    }
#endif
#if @iscomplex@
    if (@isnan@(mp_im)) {
        /* nan encountered; it's maximal */
        return 0;
    }
#endif

    for (i = 1; i < n; i++) {
        @incr@;
        /*
         * Propagate nans, similarly as max() and min()
         */
#if @iscomplex@
        /* Lexical order for complex numbers */
        if ((ip[0] > mp) || ((ip[0] == mp) && (ip[1] > mp_im))
                || @isnan@(ip[0]) || @isnan@(ip[1])) {
            mp = ip[0];
            mp_im = ip[1];
            *max_ind = i;
            if (@isnan@(mp) || @isnan@(mp_im)) {
                /* nan encountered, it's maximal */
                break;
            }
        }
#else
        if (!@le@(*ip, mp)) {  /* negated, for correct nan handling */
            mp = *ip;
            *max_ind = i;
#if @isfloat@
            if (@isnan@(mp)) {
                /* nan encountered, it's maximal */
                break;
            }
#endif
        }
#endif
    }
    return 0;
}

/**end repeat**/

static int
BOOL_argmin(npy_bool *ip, npy_intp n, npy_intp *min_ind,
            PyArrayObject *NPY_UNUSED(aip))

{
    npy_bool * p = memchr(ip, 0, n * sizeof(*ip));
    if (p == NULL) {
        *min_ind = 0;
        return 0;
    }
    *min_ind = p - ip;
    return 0;
}

/**begin repeat
 *
 * #fname = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *          LONG, ULONG, LONGLONG, ULONGLONG,
 *          HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *          CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_float, npy_double, npy_longdouble#
 * #isfloat = 0*10, 1*7#
 * #isnan = nop*10, npy_half_isnan, npy_isnan*6#
 * #le = _LESS_THAN_OR_EQUAL*10, npy_half_le, _LESS_THAN_OR_EQUAL*6#
 * #iscomplex = 0*14, 1*3#
 * #incr = ip++*14, ip+=2*3#
 */
static int
@fname@_argmin(@type@ *ip, npy_intp n, npy_intp *min_ind,
        PyArrayObject *NPY_UNUSED(aip))
{
    npy_intp i;
    @type@ mp = *ip;
#if @iscomplex@
    @type@ mp_im = ip[1];
#endif

    *min_ind = 0;

#if @isfloat@
    if (@isnan@(mp)) {
        /* nan encountered; it's minimal */
        return 0;
    }
#endif
#if @iscomplex@
    if (@isnan@(mp_im)) {
        /* nan encountered; it's minimal */
        return 0;
    }
#endif

    for (i = 1; i < n; i++) {
        @incr@;
        /*
         * Propagate nans, similarly as max() and min()
         */
#if @iscomplex@
        /* Lexical order for complex numbers */
        if ((mp > ip[0]) || ((ip[0] == mp) && (mp_im > ip[1]))
                || @isnan@(ip[0]) || @isnan@(ip[1])) {
            mp = ip[0];
            mp_im = ip[1];
            *min_ind = i;
            if (@isnan@(mp) || @isnan@(mp_im)) {
                /* nan encountered, it's minimal */
                break;
            }
        }
#else
        if (!@le@(mp, *ip)) {  /* negated, for correct nan handling */
            mp = *ip;
            *min_ind = i;
#if @isfloat@
            if (@isnan@(mp)) {
                /* nan encountered, it's minimal */
                break;
            }
#endif
        }
#endif
    }
    return 0;
}

/**end repeat**/

#undef _LESS_THAN_OR_EQUAL

/**begin repeat
 *
 * #fname = DATETIME, TIMEDELTA#
 * #type = npy_datetime, npy_timedelta#
 */
static int
@fname@_argmin(@type@ *ip, npy_intp n, npy_intp *min_ind,
        PyArrayObject *NPY_UNUSED(aip))
{
    /* NPY_DATETIME_NAT is smaller than every other value, we skip
     * it for consistency with min().
     */
    npy_intp i;
    @type@ mp = NPY_DATETIME_NAT;

    i = 0;
    while (i < n && mp == NPY_DATETIME_NAT) {
        mp = ip[i];
        i++;
    }
    if (i == n) {
        /* All NaTs: return 0 */
        *min_ind = 0;
        return 0;
    }
    *min_ind = i - 1;
    for (; i < n; i++) {
        if (mp > ip[i] && ip[i] != NPY_DATETIME_NAT) {
            mp = ip[i];
            *min_ind = i;
        }
    }
    return 0;
}

/**end repeat**/

static int
OBJECT_argmax(PyObject **ip, npy_intp n, npy_intp *max_ind,
              PyArrayObject *NPY_UNUSED(aip))
{
    npy_intp i;

    *max_ind = 0;
    /* Skip over all leading NULL entries */
    for (i = 0; i < n && ip[i] == NULL; ++i);
    if (i < n) {
        /* Found first non-NULL entry */
        PyObject *mp = ip[i];
        *max_ind = i;
        for (i = i + 1; i < n; ++i) {
            PyObject *val = ip[i];
            if (val != NULL) {
                int greater_than = PyObject_RichCompareBool(val, mp, Py_GT);

                if (greater_than < 0) {
                    return 0;
                }
                if (greater_than) {
                    mp = val;
                    *max_ind = i;
                }
            }
        }
    }

    return 0;
}

/**begin repeat
 *
 * #fname = STRING, UNICODE#
 * #type = npy_char, npy_ucs4#
 */
static int
@fname@_argmax(@type@ *ip, npy_intp n, npy_intp *max_ind, PyArrayObject *aip)
{
    npy_intp i;
    int elsize = PyArray_DESCR(aip)->elsize;
    @type@ *mp = (@type@ *)PyArray_malloc(elsize);

    if (mp == NULL) {
        return 0;
    }
    memcpy(mp, ip, elsize);
    *max_ind = 0;
    for (i = 1; i < n; i++) {
        ip += elsize / sizeof(@type@);
        if (@fname@_compare(ip, mp, aip) > 0) {
            memcpy(mp, ip, elsize);
            *max_ind = i;
        }
    }
    PyArray_free(mp);
    return 0;
}

/**end repeat**/

#define VOID_argmax NULL

static int
OBJECT_argmin(PyObject **ip, npy_intp n, npy_intp *min_ind,
              PyArrayObject *NPY_UNUSED(aip))
{
    npy_intp i;

    *min_ind = 0;
    /* Skip over all leading NULL entries */
    for (i = 0; i < n && ip[i] == NULL; ++i);
    if (i < n) {
        /* Found first non-NULL entry */
        PyObject *mp = ip[i];
        *min_ind = i;
        for (i = i + 1; i < n ; ++i) {
            PyObject *val = ip[i];
            if (val != NULL) {
                int less_than = PyObject_RichCompareBool(val, mp, Py_LT);

                if (less_than < 0) {
                    return 0;
                }
                if (less_than) {
                    mp = val;
                    *min_ind = i;
                }
            }
        }
    }

    return 0;
}

/**begin repeat
 *
 * #fname = STRING, UNICODE#
 * #type = npy_char, npy_ucs4#
 */
static int
@fname@_argmin(@type@ *ip, npy_intp n, npy_intp *min_ind, PyArrayObject *aip)
{
    npy_intp i;
    int elsize = PyArray_DESCR(aip)->elsize;
    @type@ *mp = (@type@ *)PyArray_malloc(elsize);

    if (mp==NULL) return 0;
    memcpy(mp, ip, elsize);
    *min_ind = 0;
    for(i=1; i<n; i++) {
        ip += elsize / sizeof(@type@);
        if (@fname@_compare(mp,ip,aip) > 0) {
            memcpy(mp, ip, elsize);
            *min_ind=i;
        }
    }
    PyArray_free(mp);
    return 0;
}

/**end repeat**/


#define VOID_argmin NULL


/*
 *****************************************************************************
 **                                  DOT                                    **
 *****************************************************************************
 */

/*
 * dot means inner product
 */

/************************** MAYBE USE CBLAS *********************************/


/**begin repeat
 *
 * #name = FLOAT, DOUBLE#
 * #type = npy_float, npy_double#
 * #prefix = s, d#
 */
NPY_NO_EXPORT void
@name@_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2, char *op,
           npy_intp n, void *NPY_UNUSED(ignore))
{
#if defined(HAVE_CBLAS)
    int is1b = blas_stride(is1, sizeof(@type@));
    int is2b = blas_stride(is2, sizeof(@type@));

    if (is1b && is2b)
    {
        double sum = 0.;  /* double for stability */

        while (n > 0) {
            int chunk = n < NPY_CBLAS_CHUNK ? n : NPY_CBLAS_CHUNK;

            sum += cblas_@prefix@dot(chunk,
                                     (@type@ *) ip1, is1b,
                                     (@type@ *) ip2, is2b);
            /* use char strides here */
            ip1 += chunk * is1;
            ip2 += chunk * is2;
            n -= chunk;
        }
        *((@type@ *)op) = (@type@)sum;
    }
    else
#endif
    {
        @type@ sum = (@type@)0;  /* could make this double */
        npy_intp i;

        for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
            const @type@ ip1r = *((@type@ *)ip1);
            const @type@ ip2r = *((@type@ *)ip2);

            sum += ip1r * ip2r;
        }
        *((@type@ *)op) = sum;
    }
}
/**end repeat**/

/**begin repeat
 *
 * #name = CFLOAT, CDOUBLE#
 * #ctype = npy_cfloat, npy_cdouble#
 * #type = npy_float, npy_double#
 * #prefix = c, z#
 */
NPY_NO_EXPORT void
@name@_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2,
           char *op, npy_intp n, void *NPY_UNUSED(ignore))
{
#if defined(HAVE_CBLAS)
    int is1b = blas_stride(is1, sizeof(@ctype@));
    int is2b = blas_stride(is2, sizeof(@ctype@));

    if (is1b && is2b) {
        double sum[2] = {0., 0.};  /* double for stability */

        while (n > 0) {
            int chunk = n < NPY_CBLAS_CHUNK ? n : NPY_CBLAS_CHUNK;
            @type@ tmp[2];

            cblas_@prefix@dotu_sub((int)n, ip1, is1b, ip2, is2b, tmp);
            sum[0] += (double)tmp[0];
            sum[1] += (double)tmp[1];
            /* use char strides here */
            ip1 += chunk * is1;
            ip2 += chunk * is2;
            n -= chunk;
        }
        ((@type@ *)op)[0] = (@type@)sum[0];
        ((@type@ *)op)[1] = (@type@)sum[1];
    }
    else
#endif
    {
        @type@ sumr = (@type@)0.0;
        @type@ sumi = (@type@)0.0;
        npy_intp i;

        for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
            const @type@ ip1r = ((@type@ *)ip1)[0];
            const @type@ ip1i = ((@type@ *)ip1)[1];
            const @type@ ip2r = ((@type@ *)ip2)[0];
            const @type@ ip2i = ((@type@ *)ip2)[1];

            sumr += ip1r * ip2r - ip1i * ip2i;
            sumi += ip1r * ip2i + ip1i * ip2r;
        }
        ((@type@ *)op)[0] = sumr;
        ((@type@ *)op)[1] = sumi;
    }
}

/**end repeat**/

/**************************** NO CBLAS VERSIONS *****************************/

static void
BOOL_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2, char *op, npy_intp n,
         void *NPY_UNUSED(ignore))
{
    npy_bool tmp = NPY_FALSE;
    npy_intp i;

    for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
        if ((*((npy_bool *)ip1) != 0) && (*((npy_bool *)ip2) != 0)) {
            tmp = NPY_TRUE;
            break;
        }
    }
    *((npy_bool *)op) = tmp;
}

/**begin repeat
 *
 * #name = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         LONGDOUBLE, DATETIME, TIMEDELTA#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_longdouble, npy_datetime, npy_timedelta#
 * #out = npy_long, npy_ulong, npy_long, npy_ulong, npy_long, npy_ulong,
 *        npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *        npy_longdouble, npy_datetime, npy_timedelta#
 */
static void
@name@_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2, char *op, npy_intp n,
           void *NPY_UNUSED(ignore))
{
    @out@ tmp = (@out@)0;
    npy_intp i;

    for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
        tmp += (@out@)(*((@type@ *)ip1)) *
               (@out@)(*((@type@ *)ip2));
    }
    *((@type@ *)op) = (@type@) tmp;
}
/**end repeat**/

static void
HALF_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2, char *op,
         npy_intp n, void *NPY_UNUSED(ignore))
{
    float tmp = 0.0f;
    npy_intp i;

    for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
        tmp += npy_half_to_float(*((npy_half *)ip1)) *
               npy_half_to_float(*((npy_half *)ip2));
    }
    *((npy_half *)op) = npy_float_to_half(tmp);
}

static void
CLONGDOUBLE_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2,
                            char *op, npy_intp n, void *NPY_UNUSED(ignore))
{
    npy_longdouble tmpr = 0.0L;
    npy_longdouble tmpi = 0.0L;
    npy_intp i;

    for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
        const npy_longdouble ip1r = ((npy_longdouble *)ip1)[0];
        const npy_longdouble ip1i = ((npy_longdouble *)ip1)[1];
        const npy_longdouble ip2r = ((npy_longdouble *)ip2)[0];
        const npy_longdouble ip2i = ((npy_longdouble *)ip2)[1];

        tmpr += ip1r * ip2r - ip1i * ip2i;
        tmpi += ip1r * ip2i + ip1i * ip2r;
    }
    ((npy_longdouble *)op)[0] = tmpr;
    ((npy_longdouble *)op)[1] = tmpi;
}

static void
OBJECT_dot(char *ip1, npy_intp is1, char *ip2, npy_intp is2, char *op, npy_intp n,
           void *NPY_UNUSED(ignore))
{
    /*
     * ALIGNMENT NOTE: np.dot, np.inner etc. enforce that the array is
     * BEHAVED before getting to this point, so unaligned pointers aren't
     * handled here.
     */
    npy_intp i;
    PyObject *tmp1, *tmp2, *tmp = NULL;
    PyObject **tmp3;
    for (i = 0; i < n; i++, ip1 += is1, ip2 += is2) {
        if ((*((PyObject **)ip1) == NULL) || (*((PyObject **)ip2) == NULL)) {
            tmp1 = Py_False;
            Py_INCREF(Py_False);
        }
        else {
            tmp1 = PyNumber_Multiply(*((PyObject **)ip1), *((PyObject **)ip2));
            if (!tmp1) {
                Py_XDECREF(tmp);
                return;
            }
        }
        if (i == 0) {
            tmp = tmp1;
        }
        else {
            tmp2 = PyNumber_Add(tmp, tmp1);
            Py_XDECREF(tmp);
            Py_XDECREF(tmp1);
            if (!tmp2) {
                return;
            }
            tmp = tmp2;
        }
    }
    tmp3 = (PyObject**) op;
    tmp2 = *tmp3;
    *((PyObject **)op) = tmp;
    Py_XDECREF(tmp2);
}


/*
 *****************************************************************************
 **                                 FILL                                    **
 *****************************************************************************
 */


#define BOOL_fill NULL

/* this requires buffer to be filled with objects or NULL */
static void
OBJECT_fill(PyObject **buffer, npy_intp length, void *NPY_UNUSED(ignored))
{
    npy_intp i;
    PyObject *start = buffer[0];
    PyObject *delta = buffer[1];
    PyObject *second;

    delta = PyNumber_Subtract(delta, start);
    if (!delta) {
        return;
    }
    second = start = PyNumber_Add(start, delta);
    if (!start) {
        goto finish;
    }
    buffer += 2;

    for (i = 2; i < length; i++, buffer++) {
        start = PyNumber_Add(start, delta);
        if (!start) {
            goto finish;
        }
        Py_XDECREF(*buffer);
        *buffer = start;
    }

finish:
    Py_XDECREF(second);
    Py_DECREF(delta);
    return;
}

/**begin repeat
 *
 * #NAME = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         FLOAT, DOUBLE, LONGDOUBLE,
 *         DATETIME, TIMEDELTA#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_float, npy_double, npy_longdouble,
 *         npy_datetime, npy_timedelta#
*/
static void
@NAME@_fill(@type@ *buffer, npy_intp length, void *NPY_UNUSED(ignored))
{
    npy_intp i;
    @type@ start = buffer[0];
    @type@ delta = buffer[1];

    delta -= start;
    for (i = 2; i < length; ++i) {
        buffer[i] = start + i*delta;
    }
}
/**end repeat**/

static void
HALF_fill(npy_half *buffer, npy_intp length, void *NPY_UNUSED(ignored))
{
    npy_intp i;
    float start = npy_half_to_float(buffer[0]);
    float delta = npy_half_to_float(buffer[1]);

    delta -= start;
    for (i = 2; i < length; ++i) {
        buffer[i] = npy_float_to_half(start + i*delta);
    }
}

/**begin repeat
 *
 * #NAME = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
*/
static void
@NAME@_fill(@type@ *buffer, npy_intp length, void *NPY_UNUSED(ignore))
{
    npy_intp i;
    @type@ start;
    @type@ delta;

    start.real = buffer->real;
    start.imag = buffer->imag;
    delta.real = buffer[1].real;
    delta.imag = buffer[1].imag;
    delta.real -= start.real;
    delta.imag -= start.imag;
    buffer += 2;
    for (i = 2; i < length; i++, buffer++) {
        buffer->real = start.real + i*delta.real;
        buffer->imag = start.imag + i*delta.imag;
    }
}
/**end repeat**/


/* this requires buffer to be filled with objects or NULL */
static void
OBJECT_fillwithscalar(PyObject **buffer, npy_intp length, PyObject **value,
        void *NPY_UNUSED(ignored))
{
    npy_intp i;
    PyObject *val = *value;
    for (i = 0; i < length; i++) {
        Py_XINCREF(val);
        Py_XDECREF(buffer[i]);
        buffer[i] = val;
    }
}
/**begin repeat
 *
 * #NAME = BOOL, BYTE, UBYTE#
 * #type = npy_bool, npy_byte, npy_ubyte#
 */
static void
@NAME@_fillwithscalar(@type@ *buffer, npy_intp length, @type@ *value,
        void *NPY_UNUSED(ignored))
{
    memset(buffer, *value, length);
}
/**end repeat**/

/**begin repeat
 *
 * #NAME = SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *         CFLOAT, CDOUBLE, CLONGDOUBLE,
 *         DATETIME, TIMEDELTA#
 * #type = npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_cfloat, npy_cdouble, npy_clongdouble,
 *         npy_datetime, npy_timedelta#
 */
static void
@NAME@_fillwithscalar(@type@ *buffer, npy_intp length, @type@ *value,
        void *NPY_UNUSED(ignored))
{
    npy_intp i;
    @type@ val = *value;

    for (i = 0; i < length; ++i) {
        buffer[i] = val;
    }
}
/**end repeat**/


/*
 *****************************************************************************
 **                               FASTCLIP                                  **
 *****************************************************************************
 */

#define _LESS_THAN(a, b) ((a) < (b))
#define _GREATER_THAN(a, b) ((a) > (b))

/*
 * In fastclip, 'b' was already checked for NaN, so the half comparison
 * only needs to check 'a' for NaN.
 */

#define _HALF_LESS_THAN(a, b) (!npy_half_isnan(a) && npy_half_lt_nonan(a, b))
#define _HALF_GREATER_THAN(a, b) (!npy_half_isnan(a) && npy_half_lt_nonan(b, a))

/**begin repeat
 *
 * #name = BOOL,
 *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *         DATETIME, TIMEDELTA#
 * #type = npy_bool,
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_datetime, npy_timedelta#
 * #isfloat = 0*11, 1*4, 0*2#
 * #isnan = nop*11, npy_half_isnan, npy_isnan*3, nop*2#
 * #lt = _LESS_THAN*11, _HALF_LESS_THAN, _LESS_THAN*5#
 * #gt = _GREATER_THAN*11, _HALF_GREATER_THAN, _GREATER_THAN*5#
 */
static void
@name@_fastclip(@type@ *in, npy_intp ni, @type@ *min, @type@ *max, @type@ *out)
{
    npy_intp i;
    @type@ max_val = 0, min_val = 0;

    if (max != NULL) {
        max_val = *max;
#if @isfloat@
        /* NaNs result in no clipping, so optimize the case away */
        if (@isnan@(max_val)) {
            if (min == NULL) {
                memmove(out, in, ni * sizeof(@type@));
                return;
            }
            max = NULL;
        }
#endif
    }
    if (min != NULL) {
        min_val = *min;
#if @isfloat@
        if (@isnan@(min_val)) {
            if (max == NULL) {
                memmove(out, in, ni * sizeof(@type@));
                return;
            }
            min = NULL;
        }
#endif
    }
    if (max == NULL) {
        for (i = 0; i < ni; i++) {
            if (@lt@(in[i], min_val)) {
                out[i] = min_val;
            }
            else {
                out[i] = in[i];
            }
        }
    }
    else if (min == NULL) {
        for (i = 0; i < ni; i++) {
            if (@gt@(in[i], max_val)) {
                out[i] = max_val;
            }
            else {
                out[i] = in[i];
            }
        }
    }
    else {
        /*
         * Visual Studio 2015 loop vectorizer handles NaN in an unexpected
         * manner, see: https://github.com/numpy/numpy/issues/7601
         */
        #if (_MSC_VER == 1900)
        #pragma loop( no_vector )
        #endif
        for (i = 0; i < ni; i++) {
            if (@lt@(in[i], min_val)) {
                out[i]   = min_val;
            }
            else if (@gt@(in[i], max_val)) {
                out[i]   = max_val;
            }
            else {
                out[i] = in[i];
            }
        }
    }
}
/**end repeat**/

#undef _LESS_THAN
#undef _GREATER_THAN
#undef _HALF_LESS_THAN
#undef _HALF_GREATER_THAN

/**begin repeat
 *
 * #name = CFLOAT, CDOUBLE, CLONGDOUBLE#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 */
static void
@name@_fastclip(@type@ *in, npy_intp ni, @type@ *min, @type@ *max, @type@ *out)
{
    npy_intp i;
    @type@ max_val, min_val;

    if (max != NULL) {
        max_val = *max;
    }
    if (min != NULL) {
        min_val = *min;
    }
    if (max == NULL) {
        for (i = 0; i < ni; i++) {
            if (PyArray_CLT(in[i],min_val)) {
                out[i] = min_val;
            }
            else {
                out[i] = in[i];
            }
        }
    }
    else if (min == NULL) {
        for (i = 0; i < ni; i++) {
            if (PyArray_CGT(in[i], max_val)) {
                out[i] = max_val;
            }
            else {
                out[i] = in[i];
            }
        }
    }
    else {
        for (i = 0; i < ni; i++) {
            if (PyArray_CLT(in[i], min_val)) {
                out[i] = min_val;
            }
            else if (PyArray_CGT(in[i], max_val)) {
                out[i] = max_val;
            }
            else {
                out[i] = in[i];
            }
        }
    }
}

/**end repeat**/

#define OBJECT_fastclip NULL


/*
 *****************************************************************************
 **                              FASTPUTMASK                                **
 *****************************************************************************
 */


/**begin repeat
 *
 * #name = BOOL,
 *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *         CFLOAT, CDOUBLE, CLONGDOUBLE,
 *         DATETIME, TIMEDELTA#
 * #type = npy_bool, npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_cfloat, npy_cdouble, npy_clongdouble,
 *         npy_datetime, npy_timedelta#
*/
static void
@name@_fastputmask(@type@ *in, npy_bool *mask, npy_intp ni, @type@ *vals,
        npy_intp nv)
{
    npy_intp i, j;

    if (nv == 1) {
        @type@ s_val = *vals;
        for (i = 0; i < ni; i++) {
            if (mask[i]) {
                in[i] = s_val;
            }
        }
    }
    else {
        for (i = 0, j = 0; i < ni; i++, j++) {
            if (j >= nv) {
                j = 0;
            }
            if (mask[i]) {
                in[i] = vals[j];
            }
        }
    }
    return;
}
/**end repeat**/

#define OBJECT_fastputmask NULL


/*
 *****************************************************************************
 **                                FASTTAKE                                 **
 *****************************************************************************
 */


/**begin repeat
 *
 * #name = BOOL,
 *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *         CFLOAT, CDOUBLE, CLONGDOUBLE,
 *         DATETIME, TIMEDELTA#
 * #type = npy_bool,
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_cfloat, npy_cdouble, npy_clongdouble,
 *         npy_datetime, npy_timedelta#
*/
static int
@name@_fasttake(@type@ *dest, @type@ *src, npy_intp *indarray,
                    npy_intp nindarray, npy_intp n_outer,
                    npy_intp m_middle, npy_intp nelem,
                    NPY_CLIPMODE clipmode)
{
    npy_intp i, j, k, tmp;
    NPY_BEGIN_THREADS_DEF;

    NPY_BEGIN_THREADS;

    switch(clipmode) {
    case NPY_RAISE:
        for (i = 0; i < n_outer; i++) {
            for (j = 0; j < m_middle; j++) {
                tmp = indarray[j];
                /*
                 * We don't know what axis we're operating on,
                 * so don't report it in case of an error.
                 */
                if (check_and_adjust_index(&tmp, nindarray, -1, _save) < 0) {
                    return 1;
                }
                if (NPY_LIKELY(nelem == 1)) {
                    *dest++ = *(src + tmp);
                }
                else {
                    for (k = 0; k < nelem; k++) {
                        *dest++ = *(src + tmp*nelem + k);
                    }
                }
            }
            src += nelem*nindarray;
        }
        break;
    case NPY_WRAP:
        for (i = 0; i < n_outer; i++) {
            for (j = 0; j < m_middle; j++) {
                tmp = indarray[j];
                if (tmp < 0) {
                    while (tmp < 0) {
                        tmp += nindarray;
                    }
                }
                else if (tmp >= nindarray) {
                    while (tmp >= nindarray) {
                        tmp -= nindarray;
                    }
                }
                if (NPY_LIKELY(nelem == 1)) {
                    *dest++ = *(src+tmp);
                }
                else {
                    for (k = 0; k < nelem; k++) {
                        *dest++ = *(src+tmp*nelem+k);
                    }
                }
            }
            src += nelem*nindarray;
        }
        break;
    case NPY_CLIP:
        for (i = 0; i < n_outer; i++) {
            for (j = 0; j < m_middle; j++) {
                tmp = indarray[j];
                if (tmp < 0) {
                    tmp = 0;
                }
                else if (tmp >= nindarray) {
                    tmp = nindarray - 1;
                }
                if (NPY_LIKELY(nelem == 1)) {
                    *dest++ = *(src + tmp);
                }
                else {
                    for (k = 0; k < nelem; k++) {
                        *dest++ = *(src + tmp*nelem + k);
                    }
                }
            }
            src += nelem*nindarray;
        }
        break;
    }

    NPY_END_THREADS;
    return 0;
}
/**end repeat**/

#define OBJECT_fasttake NULL

/*
 *****************************************************************************
 **                       small correlate                                   **
 *****************************************************************************
 */

/*
 * Compute correlation of data with with small kernels
 * Calling a BLAS dot product for the inner loop of the correlation is overkill
 * for small kernels. It is faster to compute it directly.
 * Intended to be used by _pyarray_correlate so no input verifications is done
 * especially it does not handle the boundaries, they should be handled by the
 * caller.
 * Returns 0 if kernel is considered too large or types are not supported, then
 * the regular array dot should be used to process the data.
 *
 * d_, dstride, nd, dtype: data pointer, its stride in bytes, number of
 *                         elements and type of data
 * k_, kstride, nk, ktype: kernel pointer, its stride in bytes, number of
 *                         elements and type of data
 * out_, ostride: output data pointer and its stride in bytes
 */
NPY_NO_EXPORT int
small_correlate(const char * d_, npy_intp dstride,
                npy_intp nd, enum NPY_TYPES dtype,
                const char * k_, npy_intp kstride,
                npy_intp nk, enum NPY_TYPES ktype,
                char * out_, npy_intp ostride)
{
    /* only handle small kernels and uniform types */
    if (nk > 11 || dtype != ktype) {
        return 0;
    }

    switch (dtype) {
/**begin repeat
 * Float types
 *  #type = npy_float, npy_double#
 *  #TYPE = NPY_FLOAT, NPY_DOUBLE#
 */
        case @TYPE@:
            {
                npy_intp i;
                const @type@ * d = (@type@*)d_;
                const @type@ * k = (@type@*)k_;
                @type@ * out = (@type@*)out_;
                dstride /= sizeof(@type@);
                kstride /= sizeof(@type@);
                ostride /= sizeof(@type@);
                /* unroll inner loop to optimize register usage of the kernel*/
                switch (nk) {
/**begin repeat1
 *  #ksz_outer = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11# */
                    case @ksz_outer@:
                    {
/**begin repeat2
 *  #ksz = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11# */
#if @ksz@ <= @ksz_outer@
                        /* load kernel */
                        const @type@ k@ksz@ = k[(@ksz@ - 1) * kstride];
#endif
/**end repeat2**/
                        for (i = 0; i < nd; i++) {
                            @type@ s = 0;
/**begin repeat2
 *  #ksz = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11# */
#if @ksz@ <= @ksz_outer@
                            s += d[(i + @ksz@ - 1) * dstride] * k@ksz@;
#endif
/**end repeat2**/
                            out[i * ostride] = s;
                        }
                        return 1;
                    }
/**end repeat1**/
                    default:
                        return 0;
                }
            }
/**end repeat**/
        default:
            return 0;
    }
}

/*
 *****************************************************************************
 **                       SETUP FUNCTION POINTERS                           **
 *****************************************************************************
 */


#define _ALIGN(type) offsetof(struct {char c; type v;}, v)
/*
 * Disable harmless compiler warning "4116: unnamed type definition in
 * parentheses" which is caused by the _ALIGN macro.
 */
#if defined(_MSC_VER)
#pragma warning(disable:4116)
#endif


/**begin repeat
 *
 * #from = VOID, STRING, UNICODE#
 * #suff = void, string, unicode#
 * #sort = 0, 1, 1#
 * #align = char, char, npy_ucs4#
 * #NAME = Void, String, Unicode#
 * #endian = |, |, =#
 * #flags = 0, 0, NPY_NEEDS_INIT#
 */
static PyArray_ArrFuncs _Py@NAME@_ArrFuncs = {
    {
        @from@_to_BOOL,
        @from@_to_BYTE,
        @from@_to_UBYTE,
        @from@_to_SHORT,
        @from@_to_USHORT,
        @from@_to_INT,
        @from@_to_UINT,
        @from@_to_LONG,
        @from@_to_ULONG,
        @from@_to_LONGLONG,
        @from@_to_ULONGLONG,
        @from@_to_FLOAT,
        @from@_to_DOUBLE,
        @from@_to_LONGDOUBLE,
        @from@_to_CFLOAT,
        @from@_to_CDOUBLE,
        @from@_to_CLONGDOUBLE,
        @from@_to_OBJECT,
        @from@_to_STRING,
        @from@_to_UNICODE,
        @from@_to_VOID
    },
    @from@_getitem,
    @from@_setitem,
    (PyArray_CopySwapNFunc*)@from@_copyswapn,
    (PyArray_CopySwapFunc*)@from@_copyswap,
    (PyArray_CompareFunc*)@from@_compare,
    (PyArray_ArgFunc*)@from@_argmax,
    (PyArray_DotFunc*)NULL,
    (PyArray_ScanFunc*)@from@_scan,
    @from@_fromstr,
    (PyArray_NonzeroFunc*)@from@_nonzero,
    (PyArray_FillFunc*)NULL,
    (PyArray_FillWithScalarFunc*)NULL,
#if @sort@
    {
        quicksort_@suff@,
        heapsort_@suff@,
        mergesort_@suff@
    },
    {
        aquicksort_@suff@,
        aheapsort_@suff@,
        amergesort_@suff@
    },
#else
    {
        NULL, NULL, NULL
    },
    {
        NULL, NULL, NULL
    },
#endif
    NULL,
    (PyArray_ScalarKindFunc*)NULL,
    NULL,
    NULL,
    (PyArray_FastClipFunc *)NULL,
    (PyArray_FastPutmaskFunc *)NULL,
    (PyArray_FastTakeFunc *)NULL,
    (PyArray_ArgFunc*)@from@_argmin
};

/*
 * FIXME: check for PY3K
 */
static PyArray_Descr @from@_Descr = {
    PyObject_HEAD_INIT(&PyArrayDescr_Type)
    /* typeobj */
    &Py@NAME@ArrType_Type,
    /* kind */
    NPY_@from@LTR,
    /* type */
    NPY_@from@LTR,
    /* byteorder */
    '@endian@',
    /* flags, unicode needs init as py3.3 does not like printing garbage  */
    @flags@,
    /* type_num */
    NPY_@from@,
    /* elsize */
    0,
    /* alignment */
    _ALIGN(@align@),
    /* subarray */
    NULL,
    /* fields */
    NULL,
    /* names */
    NULL,
    /* f */
    &_Py@NAME@_ArrFuncs,
    /* metadata */
    NULL,
    /* c_metadata */
    NULL,
    /* hash */
    -1,
};

/**end repeat**/

/**begin repeat
 *
 * #from = BOOL,
 *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *         LONG, ULONG, LONGLONG, ULONGLONG,
 *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
 *         CFLOAT, CDOUBLE, CLONGDOUBLE,
 *         OBJECT, DATETIME, TIMEDELTA#
 * #suff = bool,
 *         byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         half, float, double, longdouble,
 *         cfloat, cdouble, clongdouble,
 *         object, datetime, timedelta#
 * #sort = 1*18, 0*1, 1*2#
 * #num = 1*15, 2*3, 1*3#
 * #fromtype = npy_bool,
 *             npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_half, npy_float, npy_double, npy_longdouble,
 *             npy_float, npy_double, npy_longdouble,
 *             PyObject *, npy_datetime, npy_timedelta#
 * #NAME = Bool,
 *         Byte, UByte, Short, UShort, Int, UInt,
 *         Long, ULong, LongLong, ULongLong,
 *         Half, Float, Double, LongDouble,
 *         CFloat, CDouble, CLongDouble,
 *         Object, Datetime, Timedelta#
 * #kind = GENBOOL,
 *         SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED,
 *         SIGNED, UNSIGNED, SIGNED, UNSIGNED,
 *         FLOATING, FLOATING, FLOATING, FLOATING,
 *         COMPLEX, COMPLEX, COMPLEX,
 *         OBJECT, DATETIME, TIMEDELTA#
 * #endian = |*3, =*15, |, =*2#
 * #isobject= 0*18,NPY_OBJECT_DTYPE_FLAGS,0*2#
 */

static PyArray_ArrFuncs _Py@NAME@_ArrFuncs = {
    {
        @from@_to_BOOL,
        @from@_to_BYTE,
        @from@_to_UBYTE,
        @from@_to_SHORT,
        @from@_to_USHORT,
        @from@_to_INT,
        @from@_to_UINT,
        @from@_to_LONG,
        @from@_to_ULONG,
        @from@_to_LONGLONG,
        @from@_to_ULONGLONG,
        @from@_to_FLOAT,
        @from@_to_DOUBLE,
        @from@_to_LONGDOUBLE,
        @from@_to_CFLOAT,
        @from@_to_CDOUBLE,
        @from@_to_CLONGDOUBLE,
        @from@_to_OBJECT,
        @from@_to_STRING,
        @from@_to_UNICODE,
        @from@_to_VOID
    },
    @from@_getitem,
    @from@_setitem,
    (PyArray_CopySwapNFunc*)@from@_copyswapn,
    (PyArray_CopySwapFunc*)@from@_copyswap,
    (PyArray_CompareFunc*)@from@_compare,
    (PyArray_ArgFunc*)@from@_argmax,
    (PyArray_DotFunc*)@from@_dot,
    (PyArray_ScanFunc*)@from@_scan,
    @from@_fromstr,
    (PyArray_NonzeroFunc*)@from@_nonzero,
    (PyArray_FillFunc*)@from@_fill,
    (PyArray_FillWithScalarFunc*)@from@_fillwithscalar,
#if @sort@
    {
        quicksort_@suff@,
        heapsort_@suff@,
        mergesort_@suff@
    },
    {
        aquicksort_@suff@,
        aheapsort_@suff@,
        amergesort_@suff@
    },
#else
    {
        NULL, NULL, NULL
    },
    {
        NULL, NULL, NULL
    },
#endif
    NULL,
    (PyArray_ScalarKindFunc*)NULL,
    NULL,
    NULL,
    (PyArray_FastClipFunc*)@from@_fastclip,
    (PyArray_FastPutmaskFunc*)@from@_fastputmask,
    (PyArray_FastTakeFunc*)@from@_fasttake,
    (PyArray_ArgFunc*)@from@_argmin
};

/*
 * FIXME: check for PY3K
 */
NPY_NO_EXPORT PyArray_Descr @from@_Descr = {
    PyObject_HEAD_INIT(&PyArrayDescr_Type)
    /* typeobj */
    &Py@NAME@ArrType_Type,
    /* kind */
    NPY_@kind@LTR,
    /* type */
    NPY_@from@LTR,
    /* byteorder */
    '@endian@',
    /* flags */
    @isobject@,
    /* type_num */
    NPY_@from@,
    /* elsize */
    @num@ * sizeof(@fromtype@),
    /* alignment */
    @num@ * _ALIGN(@fromtype@) > NPY_MAX_COPY_ALIGNMENT ?
        NPY_MAX_COPY_ALIGNMENT : @num@ * _ALIGN(@fromtype@),
    /* subarray */
    NULL,
    /* fields */
    NULL,
    /* names */
    NULL,
    /* f */
    &_Py@NAME@_ArrFuncs,
    /* metadata */
    NULL,
    /* c_metadata */
    NULL,
    /* hash */
    -1,
};

/**end repeat**/

#define _MAX_LETTER 128
static char _letter_to_num[_MAX_LETTER];

static PyArray_Descr *_builtin_descrs[] = {
    &BOOL_Descr,
    &BYTE_Descr,
    &UBYTE_Descr,
    &SHORT_Descr,
    &USHORT_Descr,
    &INT_Descr,
    &UINT_Descr,
    &LONG_Descr,
    &ULONG_Descr,
    &LONGLONG_Descr,
    &ULONGLONG_Descr,
    &FLOAT_Descr,
    &DOUBLE_Descr,
    &LONGDOUBLE_Descr,
    &CFLOAT_Descr,
    &CDOUBLE_Descr,
    &CLONGDOUBLE_Descr,
    &OBJECT_Descr,
    &STRING_Descr,
    &UNICODE_Descr,
    &VOID_Descr,
    &DATETIME_Descr,
    &TIMEDELTA_Descr,
    &HALF_Descr
};

/*NUMPY_API
 * Get the PyArray_Descr structure for a type.
 */
NPY_NO_EXPORT PyArray_Descr *
PyArray_DescrFromType(int type)
{
    PyArray_Descr *ret = NULL;

    if (type < NPY_NTYPES) {
        ret = _builtin_descrs[type];
    }
    else if (type == NPY_NOTYPE) {
        /*
         * This needs to not raise an error so
         * that PyArray_DescrFromType(NPY_NOTYPE)
         * works for backwards-compatible C-API
         */
        return NULL;
    }
    else if ((type == NPY_CHAR) || (type == NPY_CHARLTR)) {
        if (type == NPY_CHAR) {
            /*
             * warning added 2017-04-25, 1.13
             * deprecated in 1.7
             * */
            if (DEPRECATE("The NPY_CHAR type_num is deprecated. "
                          "Please port your code to use "
                          "NPY_STRING instead.") < 0) {
                return NULL;
            }
        }
        ret = PyArray_DescrNew(_builtin_descrs[NPY_STRING]);
        if (ret == NULL) {
            return NULL;
        }
        ret->elsize = 1;
        ret->type = NPY_CHARLTR;
        return ret;
    }
    else if (PyTypeNum_ISUSERDEF(type)) {
        ret = userdescrs[type - NPY_USERDEF];
    }
    else {
        int num = NPY_NTYPES;
        if (type < _MAX_LETTER) {
            num = (int) _letter_to_num[type];
        }
        if (num >= NPY_NTYPES) {
            ret = NULL;
        }
        else {
            ret = _builtin_descrs[num];
        }
    }
    if (ret == NULL) {
        PyErr_SetString(PyExc_ValueError,
                "Invalid data-type for array");
    }
    else {
        Py_INCREF(ret);
    }

    return ret;
}

/* A clone function for the datetime dtype metadata */
static NpyAuxData *
datetime_dtype_metadata_clone(NpyAuxData *data)
{
    PyArray_DatetimeDTypeMetaData *newdata =
        (PyArray_DatetimeDTypeMetaData *)PyArray_malloc(
                        sizeof(PyArray_DatetimeDTypeMetaData));
    if (newdata == NULL) {
        return NULL;
    }

    memcpy(newdata, data, sizeof(PyArray_DatetimeDTypeMetaData));

    return (NpyAuxData *)newdata;
}

/*
 * Initializes the c_metadata field for the _builtin_descrs DATETIME
 * and TIMEDELTA.
 *
 * must not be static, gcc 4.1.2 on redhat 5 then miscompiles this function
 * see gh-5163
 *
 */
NPY_NO_EXPORT int
initialize_builtin_datetime_metadata(void)
{
    PyArray_DatetimeDTypeMetaData *data1, *data2;

    /* Allocate memory for the metadata */
    data1 = PyArray_malloc(sizeof(PyArray_DatetimeDTypeMetaData));
    if (data1 == NULL) {
        return -1;
    }
    data2 = PyArray_malloc(sizeof(PyArray_DatetimeDTypeMetaData));
    if (data2 == NULL) {
        PyArray_free(data1);
        return -1;
    }

    /* Initialize the base aux data */
    memset(data1, 0, sizeof(PyArray_DatetimeDTypeMetaData));
    memset(data2, 0, sizeof(PyArray_DatetimeDTypeMetaData));
    data1->base.free = (NpyAuxData_FreeFunc *)PyArray_free;
    data2->base.free = (NpyAuxData_FreeFunc *)PyArray_free;
    data1->base.clone = datetime_dtype_metadata_clone;
    data2->base.clone = datetime_dtype_metadata_clone;

    /* Set to the default metadata */
    data1->meta.base = NPY_DATETIME_DEFAULTUNIT;
    data1->meta.num = 1;
    data2->meta.base = NPY_DATETIME_DEFAULTUNIT;
    data2->meta.num = 1;

    _builtin_descrs[NPY_DATETIME]->c_metadata = (NpyAuxData *)data1;
    _builtin_descrs[NPY_TIMEDELTA]->c_metadata = (NpyAuxData *)data2;

    return 0;
}

/*
 *****************************************************************************
 **                             SETUP TYPE INFO                             **
 *****************************************************************************
 */


/*
 * This function is called during numpy module initialization,
 * and is used to initialize internal dtype tables.
 */
NPY_NO_EXPORT int
set_typeinfo(PyObject *dict)
{
    PyObject *infodict, *s;
    int i;

    PyArray_Descr *dtype;
    PyObject *cobj, *key;

    /*
     * Add cast functions for the new types
     */

    /**begin repeat
     *
     * #name1 = BOOL,
     *          BYTE, UBYTE, SHORT, USHORT, INT, UINT,
     *          LONG, ULONG, LONGLONG, ULONGLONG,
     *          HALF, FLOAT, DOUBLE, LONGDOUBLE,
     *          CFLOAT, CDOUBLE, CLONGDOUBLE,
     *          OBJECT, STRING, UNICODE, VOID,
     *          DATETIME,TIMEDELTA#
     */

    /**begin repeat1
     *
     * #name2 = HALF, DATETIME, TIMEDELTA#
     */

    dtype = _builtin_descrs[NPY_@name1@];
    if (dtype->f->castdict == NULL) {
        dtype->f->castdict = PyDict_New();
        if (dtype->f->castdict == NULL) {
            return -1;
        }
    }
    key = PyInt_FromLong(NPY_@name2@);
    if (key == NULL) {
        return -1;
    }
    cobj = NpyCapsule_FromVoidPtr((void *)@name1@_to_@name2@, NULL);
    if (cobj == NULL) {
        Py_DECREF(key);
        return -1;
    }
    if (PyDict_SetItem(dtype->f->castdict, key, cobj) < 0) {
        Py_DECREF(key);
        Py_DECREF(cobj);
        return -1;
    }
    Py_DECREF(key);
    Py_DECREF(cobj);

    /**end repeat1**/

    /**end repeat**/

    if (initialize_builtin_datetime_metadata() < 0) {
        return -1;
    }

    for (i = 0; i < _MAX_LETTER; i++) {
        _letter_to_num[i] = NPY_NTYPES;
    }

    /**begin repeat
     *
     * #name = BOOL,
     *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
     *         INTP, UINTP,
     *         LONG, ULONG, LONGLONG, ULONGLONG,
     *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
     *         CFLOAT, CDOUBLE, CLONGDOUBLE,
     *         OBJECT, STRING, UNICODE, VOID,
     *         DATETIME,TIMEDELTA#
     */

    _letter_to_num[NPY_@name@LTR] = NPY_@name@;

    /**end repeat**/

    _letter_to_num[NPY_STRINGLTR2] = NPY_STRING;

    /**begin repeat
      * #name = BOOL,
      *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
      *         LONG, ULONG, LONGLONG, ULONGLONG,
      *         HALF, FLOAT, DOUBLE, LONGDOUBLE,
      *         CFLOAT, CDOUBLE, CLONGDOUBLE,
      *         OBJECT, STRING, UNICODE, VOID,
      *         DATETIME, TIMEDELTA#
      */

    @name@_Descr.fields = Py_None;

    /**end repeat**/


    /**begin repeat
      * #name = STRING, UNICODE, VOID#
      */

    PyDataType_MAKEUNSIZED(&@name@_Descr);

    /**end repeat**/

    /* Set a dictionary with type information */
    infodict = PyDict_New();
    if (infodict == NULL) return -1;


    /**begin repeat
     *
     * #name = BOOL,
     *         BYTE, UBYTE, SHORT, USHORT, INT, UINT,
     *         INTP, UINTP,
     *         LONG, ULONG, LONGLONG, ULONGLONG#
     * #uname = BOOL,
     *          BYTE*2, SHORT*2, INT*2,
     *          INTP*2,
     *          LONG*2, LONGLONG*2#
     * #Name = Bool,
     *         Byte, UByte, Short, UShort, Int, UInt,
     *         Intp, UIntp,
     *         Long, ULong, LongLong, ULongLong#
     * #type = npy_bool,
     *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
     *         npy_intp, npy_uintp,
     *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
     * #max= 1,
     *       NPY_MAX_BYTE, NPY_MAX_UBYTE, NPY_MAX_SHORT,
     *       NPY_MAX_USHORT, NPY_MAX_INT, PyLong_FromUnsignedLong(NPY_MAX_UINT),
     *       PyLong_FromLongLong((npy_longlong) NPY_MAX_INTP),
     *       PyLong_FromUnsignedLongLong((npy_ulonglong) NPY_MAX_UINTP),
     *       NPY_MAX_LONG,
     *       PyLong_FromUnsignedLong((npy_ulong) NPY_MAX_ULONG),
     *       PyLong_FromLongLong((npy_longlong) NPY_MAX_LONGLONG),
     *       PyLong_FromUnsignedLongLong((npy_ulonglong) NPY_MAX_ULONGLONG)#
     * #min = 0, NPY_MIN_BYTE, 0, NPY_MIN_SHORT, 0, NPY_MIN_INT, 0,
     *        PyLong_FromLongLong((npy_longlong) NPY_MIN_INTP),
     *        0, NPY_MIN_LONG, 0,
     *        PyLong_FromLongLong((npy_longlong) NPY_MIN_LONGLONG), 0#
     * #cx = i*6, N, N, N, l, N, N, N#
     * #cn = i*7, N, i, l, i, N, i#
     */

    PyDict_SetItemString(infodict, "@name@",
#if defined(NPY_PY3K)
            s = Py_BuildValue("Ciii@cx@@cn@O",
#else
            s = Py_BuildValue("ciii@cx@@cn@O",
#endif
                NPY_@name@LTR,
                NPY_@name@,
                NPY_BITSOF_@uname@,
                _ALIGN(@type@),
                @max@,
                @min@,
                (PyObject *) &Py@Name@ArrType_Type));
    Py_DECREF(s);

    /**end repeat**/


    /**begin repeat
     *
     * #type = npy_half, npy_float, npy_double, npy_longdouble,
     *         npy_cfloat, npy_cdouble, npy_clongdouble#
     * #name = HALF, FLOAT, DOUBLE, LONGDOUBLE,
     *         CFLOAT, CDOUBLE, CLONGDOUBLE#
     * #Name = Half, Float, Double, LongDouble,
     *         CFloat, CDouble, CLongDouble#
     * #num  = 1, 1, 1, 1, 2, 2, 2#
     */

    PyDict_SetItemString(infodict, "@name@",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiO", NPY_@name@LTR,
#else
            s = Py_BuildValue("ciiiO", NPY_@name@LTR,
#endif
                NPY_@name@,
                NPY_BITSOF_@name@,
                @num@ * _ALIGN(@type@) > NPY_MAX_COPY_ALIGNMENT ?
                    NPY_MAX_COPY_ALIGNMENT : @num@ * _ALIGN(@type@),
                (PyObject *) &Py@Name@ArrType_Type));
    Py_DECREF(s);

    /**end repeat**/

    PyDict_SetItemString(infodict, "OBJECT",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiO", NPY_OBJECTLTR,
#else
            s = Py_BuildValue("ciiiO", NPY_OBJECTLTR,
#endif
                NPY_OBJECT,
                sizeof(PyObject *) * CHAR_BIT,
                _ALIGN(PyObject *),
                (PyObject *) &PyObjectArrType_Type));
    Py_DECREF(s);
    PyDict_SetItemString(infodict, "STRING",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiO", NPY_STRINGLTR,
#else
            s = Py_BuildValue("ciiiO", NPY_STRINGLTR,
#endif
                NPY_STRING,
                0,
                _ALIGN(char),
                (PyObject *) &PyStringArrType_Type));
    Py_DECREF(s);
    PyDict_SetItemString(infodict, "UNICODE",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiO", NPY_UNICODELTR,
#else
            s = Py_BuildValue("ciiiO", NPY_UNICODELTR,
#endif
                NPY_UNICODE,
                0,
                _ALIGN(npy_ucs4),
                (PyObject *) &PyUnicodeArrType_Type));
    Py_DECREF(s);
    PyDict_SetItemString(infodict, "VOID",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiO", NPY_VOIDLTR,
#else
            s = Py_BuildValue("ciiiO", NPY_VOIDLTR,
#endif
                NPY_VOID,
                0,
                _ALIGN(char),
                (PyObject *) &PyVoidArrType_Type));
    Py_DECREF(s);
    PyDict_SetItemString(infodict, "DATETIME",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiNNO", NPY_DATETIMELTR,
#else
            s = Py_BuildValue("ciiiNNO", NPY_DATETIMELTR,
#endif
                NPY_DATETIME,
                NPY_BITSOF_DATETIME,
                _ALIGN(npy_datetime),
                MyPyLong_FromInt64(NPY_MAX_DATETIME),
                MyPyLong_FromInt64(NPY_MIN_DATETIME),
                (PyObject *) &PyDatetimeArrType_Type));
    Py_DECREF(s);
    PyDict_SetItemString(infodict, "TIMEDELTA",
#if defined(NPY_PY3K)
            s = Py_BuildValue("CiiiNNO", NPY_TIMEDELTALTR,
#else
            s = Py_BuildValue("ciiiNNO",NPY_TIMEDELTALTR,
#endif
                NPY_TIMEDELTA,
                NPY_BITSOF_TIMEDELTA,
                _ALIGN(npy_timedelta),
                MyPyLong_FromInt64(NPY_MAX_TIMEDELTA),
                MyPyLong_FromInt64(NPY_MIN_TIMEDELTA),
                (PyObject *)&PyTimedeltaArrType_Type));
    Py_DECREF(s);

#define SETTYPE(name)                           \
    Py_INCREF(&Py##name##ArrType_Type);         \
    PyDict_SetItemString(infodict, #name,       \
            (PyObject *)&Py##name##ArrType_Type)

    SETTYPE(Generic);
    SETTYPE(Number);
    SETTYPE(Integer);
    SETTYPE(Inexact);
    SETTYPE(SignedInteger);
    SETTYPE(UnsignedInteger);
    SETTYPE(Floating);
    SETTYPE(ComplexFloating);
    SETTYPE(Flexible);
    SETTYPE(Character);

#undef SETTYPE

    PyDict_SetItemString(dict, "typeinfo", infodict);
    Py_DECREF(infodict);
    return 0;
}

#undef _MAX_LETTER
