#line 1 "bottleneck/src/nonreduce_axis_template.c"
// Copyright 2010-2019 Keith Goodman
// Copyright 2019 Bottleneck Developers
#include "bottleneck.h"
#include "iterators.h"

/* function signatures --------------------------------------------------- */

/* low-level functions such as move_sum_float64 */
#define NRA(name, dtype) \
    static PyObject * \
    name##_##dtype(PyArrayObject *a, int axis, int n)

/* top-level functions such as move_sum */
#define NRA_MAIN(name, parse) \
    static PyObject * \
    name(PyObject *self, PyObject *args, PyObject *kwds) \
    { \
        return nonreducer_axis(#name, \
                               args, \
                               kwds, \
                               name##_float64, \
                               name##_float32, \
                               name##_int64, \
                               name##_int32, \
                               parse); \
    }

/* typedefs and prototypes ----------------------------------------------- */

/* how should input be parsed? */
typedef enum {PARSE_PARTITION, PARSE_RANKDATA, PARSE_PUSH} parse_type;

/* function pointer for functions passed to nonreducer_axis */
typedef PyObject *(*nra_t)(PyArrayObject *, int, int);

static PyObject *
nonreducer_axis(char *name,
                PyObject *args,
                PyObject *kwds,
                nra_t,
                nra_t,
                nra_t,
                nra_t,
                parse_type);

/* partition ------------------------------------------------------------- */

#define B(dtype, i) AX(dtype, i) /* used by PARTITION */

#line 51
NRA(partition, float64) {
    npy_intp i;
    npy_intp j, l, r, k;
    iter it;

    a = (PyArrayObject *)PyArray_NewCopy(a, NPY_ANYORDER);
    init_iter_one(&it, a, axis);

    if (LENGTH == 0) return (PyObject *)a;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }

    BN_BEGIN_ALLOW_THREADS
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        PARTITION(float64)
        NEXT
    }
    BN_END_ALLOW_THREADS

    return (PyObject *)a;
}

#line 51
NRA(partition, float32) {
    npy_intp i;
    npy_intp j, l, r, k;
    iter it;

    a = (PyArrayObject *)PyArray_NewCopy(a, NPY_ANYORDER);
    init_iter_one(&it, a, axis);

    if (LENGTH == 0) return (PyObject *)a;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }

    BN_BEGIN_ALLOW_THREADS
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        PARTITION(float32)
        NEXT
    }
    BN_END_ALLOW_THREADS

    return (PyObject *)a;
}

#line 51
NRA(partition, int64) {
    npy_intp i;
    npy_intp j, l, r, k;
    iter it;

    a = (PyArrayObject *)PyArray_NewCopy(a, NPY_ANYORDER);
    init_iter_one(&it, a, axis);

    if (LENGTH == 0) return (PyObject *)a;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }

    BN_BEGIN_ALLOW_THREADS
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        PARTITION(int64)
        NEXT
    }
    BN_END_ALLOW_THREADS

    return (PyObject *)a;
}

#line 51
NRA(partition, int32) {
    npy_intp i;
    npy_intp j, l, r, k;
    iter it;

    a = (PyArrayObject *)PyArray_NewCopy(a, NPY_ANYORDER);
    init_iter_one(&it, a, axis);

    if (LENGTH == 0) return (PyObject *)a;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }

    BN_BEGIN_ALLOW_THREADS
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        PARTITION(int32)
        NEXT
    }
    BN_END_ALLOW_THREADS

    return (PyObject *)a;
}

NRA_MAIN(partition, PARSE_PARTITION)

/* argpartition ----------------------------------------------------------- */

#define BUFFER_NEW(dtype) dtype *B = malloc(LENGTH * sizeof(dtype));
#define BUFFER_DELETE free(B);

#define ARGWIRTH(dtype0, dtype1) \
    x = B[k]; \
    i = l; \
    j = r; \
    do { \
        while (B[i] < x) i++; \
        while (x < B[j]) j--; \
        if (i <= j) { \
            npy_##dtype0 atmp = B[i]; \
            B[i] = B[j]; \
            B[j] = atmp; \
            ytmp = YX(dtype1, i); \
            YX(dtype1, i) = YX(dtype1, j); \
            YX(dtype1, j) = ytmp; \
            i++; \
            j--; \
        } \
    } while (i <= j); \
    if (j < k) l = i; \
    if (k < i) r = j;

#define ARGPARTITION(dtype0, dtype1) \
    while (l < r) { \
        npy_##dtype0 x; \
        npy_##dtype0 al = B[l]; \
        npy_##dtype0 ak = B[k]; \
        npy_##dtype0 ar = B[r]; \
        npy_##dtype1 ytmp; \
        if (al > ak) { \
            if (ak < ar) { \
                if (al < ar) { \
                    B[k] = al; \
                    B[l] = ak; \
                    ytmp = YX(dtype1, k); \
                    YX(dtype1, k) = YX(dtype1, l); \
                    YX(dtype1, l) = ytmp; \
                } else { \
                    B[k] = ar; \
                    B[r] = ak; \
                    ytmp = YX(dtype1, k); \
                    YX(dtype1, k) = YX(dtype1, r); \
                    YX(dtype1, r) = ytmp; \
                } \
            } \
        } else { \
            if (ak > ar) { \
                if (al > ar) { \
                    B[k] = al; \
                    B[l] = ak; \
                    ytmp = YX(dtype1, k); \
                    YX(dtype1, k) = YX(dtype1, l); \
                    YX(dtype1, l) = ytmp; \
                } else { \
                    B[k] = ar; \
                    B[r] = ak; \
                    ytmp = YX(dtype1, k); \
                    YX(dtype1, k) = YX(dtype1, r); \
                    YX(dtype1, r) = ytmp; \
                } \
            } \
        } \
        ARGWIRTH(dtype0, dtype1) \
    }

#define ARGPARTSORT(dtype0, dtype1) \
    for (i = 0; i < LENGTH; i++) { \
        B[i] = AX(dtype0, i); \
        YX(dtype1, i) = i; \
    } \
    l = 0; \
    r = LENGTH - 1; \
    ARGPARTITION(dtype0, dtype1)

#line 164
NRA(argpartition, float64) {
    npy_intp i;
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a), PyArray_SHAPE(a),
                                NPY_intp, 0);
    iter2 it;
    init_iter2(&it, a, y, axis);
    if (LENGTH == 0) return y;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }
    BN_BEGIN_ALLOW_THREADS
    BUFFER_NEW(npy_float64)
    npy_intp j, l, r, k;
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        ARGPARTSORT(float64, intp)
        NEXT2
    }
    BUFFER_DELETE
    BN_END_ALLOW_THREADS
    return y;
}

#line 164
NRA(argpartition, float32) {
    npy_intp i;
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a), PyArray_SHAPE(a),
                                NPY_intp, 0);
    iter2 it;
    init_iter2(&it, a, y, axis);
    if (LENGTH == 0) return y;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }
    BN_BEGIN_ALLOW_THREADS
    BUFFER_NEW(npy_float32)
    npy_intp j, l, r, k;
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        ARGPARTSORT(float32, intp)
        NEXT2
    }
    BUFFER_DELETE
    BN_END_ALLOW_THREADS
    return y;
}

#line 164
NRA(argpartition, int64) {
    npy_intp i;
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a), PyArray_SHAPE(a),
                                NPY_intp, 0);
    iter2 it;
    init_iter2(&it, a, y, axis);
    if (LENGTH == 0) return y;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }
    BN_BEGIN_ALLOW_THREADS
    BUFFER_NEW(npy_int64)
    npy_intp j, l, r, k;
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        ARGPARTSORT(int64, intp)
        NEXT2
    }
    BUFFER_DELETE
    BN_END_ALLOW_THREADS
    return y;
}

#line 164
NRA(argpartition, int32) {
    npy_intp i;
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a), PyArray_SHAPE(a),
                                NPY_intp, 0);
    iter2 it;
    init_iter2(&it, a, y, axis);
    if (LENGTH == 0) return y;
    if (n < 0 || n > LENGTH - 1) {
        PyErr_Format(PyExc_ValueError,
                     "`n` (=%d) must be between 0 and %zd, inclusive.",
                     n, LENGTH - 1);
        return NULL;
    }
    BN_BEGIN_ALLOW_THREADS
    BUFFER_NEW(npy_int32)
    npy_intp j, l, r, k;
    k = n;
    WHILE {
        l = 0;
        r = LENGTH - 1;
        ARGPARTSORT(int32, intp)
        NEXT2
    }
    BUFFER_DELETE
    BN_END_ALLOW_THREADS
    return y;
}

NRA_MAIN(argpartition, PARSE_PARTITION)

/* rankdata -------------------------------------------------------------- */

#line 200
NRA(rankdata, float64) {
    Py_ssize_t j=0, k, idx, dupcount=0, i;
    npy_float64 old, new, averank, sumranks = 0;

    PyObject *z = PyArray_ArgSort(a, axis, NPY_QUICKSORT);
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a),
                                PyArray_SHAPE(a), NPY_float64, 0);

    iter3 it;
    init_iter3(&it, a, y, z, axis);

    BN_BEGIN_ALLOW_THREADS
    if (LENGTH == 0) {
        Py_ssize_t size = PyArray_SIZE((PyArrayObject *)y);
        npy_float64 *py = (npy_float64 *)PyArray_DATA(a);
        for (i = 0; i < size; i++) YPP = BN_NAN;
    } else {
        WHILE {
            idx = ZX(intp, 0);
            old = AX(float64, idx);
            sumranks = 0;
            dupcount = 0;
            for (i = 0; i < LENGTH - 1; i++) {
                sumranks += i;
                dupcount++;
                k = i + 1;
                idx = ZX(intp, k);
                new = AX(float64, idx);
                if (old != new) {
                    averank = sumranks / dupcount + 1;
                    for (j = k - dupcount; j < k; j++) {
                        idx = ZX(intp, j);
                        YX(float64, idx) = averank;
                    }
                    sumranks = 0;
                    dupcount = 0;
                }
                old = new;
            }
            sumranks += (LENGTH - 1);
            dupcount++;
            averank = sumranks / dupcount + 1;
            for (j = LENGTH - dupcount; j < LENGTH; j++) {
                idx = ZX(intp, j);
                YX(float64, idx) = averank;
            }
            NEXT3
        }
    }
    BN_END_ALLOW_THREADS

    Py_DECREF(z);
    return y;
}

#line 200
NRA(rankdata, float32) {
    Py_ssize_t j=0, k, idx, dupcount=0, i;
    npy_float64 old, new, averank, sumranks = 0;

    PyObject *z = PyArray_ArgSort(a, axis, NPY_QUICKSORT);
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a),
                                PyArray_SHAPE(a), NPY_float64, 0);

    iter3 it;
    init_iter3(&it, a, y, z, axis);

    BN_BEGIN_ALLOW_THREADS
    if (LENGTH == 0) {
        Py_ssize_t size = PyArray_SIZE((PyArrayObject *)y);
        npy_float64 *py = (npy_float64 *)PyArray_DATA(a);
        for (i = 0; i < size; i++) YPP = BN_NAN;
    } else {
        WHILE {
            idx = ZX(intp, 0);
            old = AX(float32, idx);
            sumranks = 0;
            dupcount = 0;
            for (i = 0; i < LENGTH - 1; i++) {
                sumranks += i;
                dupcount++;
                k = i + 1;
                idx = ZX(intp, k);
                new = AX(float32, idx);
                if (old != new) {
                    averank = sumranks / dupcount + 1;
                    for (j = k - dupcount; j < k; j++) {
                        idx = ZX(intp, j);
                        YX(float64, idx) = averank;
                    }
                    sumranks = 0;
                    dupcount = 0;
                }
                old = new;
            }
            sumranks += (LENGTH - 1);
            dupcount++;
            averank = sumranks / dupcount + 1;
            for (j = LENGTH - dupcount; j < LENGTH; j++) {
                idx = ZX(intp, j);
                YX(float64, idx) = averank;
            }
            NEXT3
        }
    }
    BN_END_ALLOW_THREADS

    Py_DECREF(z);
    return y;
}

#line 200
NRA(rankdata, int64) {
    Py_ssize_t j=0, k, idx, dupcount=0, i;
    npy_float64 old, new, averank, sumranks = 0;

    PyObject *z = PyArray_ArgSort(a, axis, NPY_QUICKSORT);
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a),
                                PyArray_SHAPE(a), NPY_float64, 0);

    iter3 it;
    init_iter3(&it, a, y, z, axis);

    BN_BEGIN_ALLOW_THREADS
    if (LENGTH == 0) {
        Py_ssize_t size = PyArray_SIZE((PyArrayObject *)y);
        npy_float64 *py = (npy_float64 *)PyArray_DATA(a);
        for (i = 0; i < size; i++) YPP = BN_NAN;
    } else {
        WHILE {
            idx = ZX(intp, 0);
            old = AX(int64, idx);
            sumranks = 0;
            dupcount = 0;
            for (i = 0; i < LENGTH - 1; i++) {
                sumranks += i;
                dupcount++;
                k = i + 1;
                idx = ZX(intp, k);
                new = AX(int64, idx);
                if (old != new) {
                    averank = sumranks / dupcount + 1;
                    for (j = k - dupcount; j < k; j++) {
                        idx = ZX(intp, j);
                        YX(float64, idx) = averank;
                    }
                    sumranks = 0;
                    dupcount = 0;
                }
                old = new;
            }
            sumranks += (LENGTH - 1);
            dupcount++;
            averank = sumranks / dupcount + 1;
            for (j = LENGTH - dupcount; j < LENGTH; j++) {
                idx = ZX(intp, j);
                YX(float64, idx) = averank;
            }
            NEXT3
        }
    }
    BN_END_ALLOW_THREADS

    Py_DECREF(z);
    return y;
}

#line 200
NRA(rankdata, int32) {
    Py_ssize_t j=0, k, idx, dupcount=0, i;
    npy_float64 old, new, averank, sumranks = 0;

    PyObject *z = PyArray_ArgSort(a, axis, NPY_QUICKSORT);
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a),
                                PyArray_SHAPE(a), NPY_float64, 0);

    iter3 it;
    init_iter3(&it, a, y, z, axis);

    BN_BEGIN_ALLOW_THREADS
    if (LENGTH == 0) {
        Py_ssize_t size = PyArray_SIZE((PyArrayObject *)y);
        npy_float64 *py = (npy_float64 *)PyArray_DATA(a);
        for (i = 0; i < size; i++) YPP = BN_NAN;
    } else {
        WHILE {
            idx = ZX(intp, 0);
            old = AX(int32, idx);
            sumranks = 0;
            dupcount = 0;
            for (i = 0; i < LENGTH - 1; i++) {
                sumranks += i;
                dupcount++;
                k = i + 1;
                idx = ZX(intp, k);
                new = AX(int32, idx);
                if (old != new) {
                    averank = sumranks / dupcount + 1;
                    for (j = k - dupcount; j < k; j++) {
                        idx = ZX(intp, j);
                        YX(float64, idx) = averank;
                    }
                    sumranks = 0;
                    dupcount = 0;
                }
                old = new;
            }
            sumranks += (LENGTH - 1);
            dupcount++;
            averank = sumranks / dupcount + 1;
            for (j = LENGTH - dupcount; j < LENGTH; j++) {
                idx = ZX(intp, j);
                YX(float64, idx) = averank;
            }
            NEXT3
        }
    }
    BN_END_ALLOW_THREADS

    Py_DECREF(z);
    return y;
}

NRA_MAIN(rankdata, PARSE_RANKDATA)

/* nanrankdata ----------------------------------------------------------- */

#line 262
NRA(nanrankdata, float64) {
    Py_ssize_t j=0, k, idx, dupcount=0, i;
    npy_float64 old, new, averank, sumranks = 0;

    PyObject *z = PyArray_ArgSort(a, axis, NPY_QUICKSORT);
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a),
                                PyArray_SHAPE(a), NPY_float64, 0);

    iter3 it;
    init_iter3(&it, a, y, z, axis);

    BN_BEGIN_ALLOW_THREADS
    if (LENGTH == 0) {
        Py_ssize_t size = PyArray_SIZE((PyArrayObject *)y);
        npy_float64 *py = (npy_float64 *)PyArray_DATA(a);
        for (i = 0; i < size; i++) YPP = BN_NAN;
    } else {
        WHILE {
            idx = ZX(intp, 0);
            old = AX(float64, idx);
            sumranks = 0;
            dupcount = 0;
            for (i = 0; i < LENGTH - 1; i++) {
                sumranks += i;
                dupcount++;
                k = i + 1;
                idx = ZX(intp, k);
                new = AX(float64, idx);
                if (old != new) {
                    if (old == old) {
                        averank = sumranks / dupcount + 1;
                        for (j = k - dupcount; j < k; j++) {
                            idx = ZX(intp, j);
                            YX(float64, idx) = averank;
                        }
                    } else {
                        idx = ZX(intp, i);
                        YX(float64, idx) = BN_NAN;
                    }
                    sumranks = 0;
                    dupcount = 0;
                }
                old = new;
            }
            sumranks += (LENGTH - 1);
            dupcount++;
            averank = sumranks / dupcount + 1;
            if (old == old) {
                for (j = LENGTH - dupcount; j < LENGTH; j++) {
                    idx = ZX(intp, j);
                    YX(float64, idx) = averank;
                }
            } else {
                idx = ZX(intp, LENGTH - 1);
                YX(float64, idx) = BN_NAN;
            }
            NEXT3
        }
    }
    BN_END_ALLOW_THREADS

    Py_DECREF(z);
    return y;
}

#line 262
NRA(nanrankdata, float32) {
    Py_ssize_t j=0, k, idx, dupcount=0, i;
    npy_float64 old, new, averank, sumranks = 0;

    PyObject *z = PyArray_ArgSort(a, axis, NPY_QUICKSORT);
    PyObject *y = PyArray_EMPTY(PyArray_NDIM(a),
                                PyArray_SHAPE(a), NPY_float64, 0);

    iter3 it;
    init_iter3(&it, a, y, z, axis);

    BN_BEGIN_ALLOW_THREADS
    if (LENGTH == 0) {
        Py_ssize_t size = PyArray_SIZE((PyArrayObject *)y);
        npy_float64 *py = (npy_float64 *)PyArray_DATA(a);
        for (i = 0; i < size; i++) YPP = BN_NAN;
    } else {
        WHILE {
            idx = ZX(intp, 0);
            old = AX(float32, idx);
            sumranks = 0;
            dupcount = 0;
            for (i = 0; i < LENGTH - 1; i++) {
                sumranks += i;
                dupcount++;
                k = i + 1;
                idx = ZX(intp, k);
                new = AX(float32, idx);
                if (old != new) {
                    if (old == old) {
                        averank = sumranks / dupcount + 1;
                        for (j = k - dupcount; j < k; j++) {
                            idx = ZX(intp, j);
                            YX(float64, idx) = averank;
                        }
                    } else {
                        idx = ZX(intp, i);
                        YX(float64, idx) = BN_NAN;
                    }
                    sumranks = 0;
                    dupcount = 0;
                }
                old = new;
            }
            sumranks += (LENGTH - 1);
            dupcount++;
            averank = sumranks / dupcount + 1;
            if (old == old) {
                for (j = LENGTH - dupcount; j < LENGTH; j++) {
                    idx = ZX(intp, j);
                    YX(float64, idx) = averank;
                }
            } else {
                idx = ZX(intp, LENGTH - 1);
                YX(float64, idx) = BN_NAN;
            }
            NEXT3
        }
    }
    BN_END_ALLOW_THREADS

    Py_DECREF(z);
    return y;
}

#line 328
static PyObject *
nanrankdata(PyObject *self, PyObject *args, PyObject *kwds) {
    return nonreducer_axis("nanrankdata",
                           args,
                           kwds,
                           nanrankdata_float64,
                           nanrankdata_float32,
                           rankdata_int64,
                           rankdata_int32,
                           PARSE_RANKDATA);
}

/* push ------------------------------------------------------------------ */

#line 344
NRA(push, float64) {
    npy_intp index;
    npy_float64 ai, ai_last, n_float;
    PyObject *y = PyArray_Copy(a);
    iter it;
    init_iter_one(&it, (PyArrayObject *)y, axis);
    if (LENGTH == 0 || NDIM == 0) {
        return y;
    }
    n_float = n < 0 ? BN_INFINITY : (npy_float64)n;
    BN_BEGIN_ALLOW_THREADS
    WHILE {
        index = 0;
        ai_last = BN_NAN;
        FOR {
            ai = AI(float64);
            if (ai == ai) {
                ai_last = ai;
                index = INDEX;
            } else {
                if (INDEX - index <= n_float) {
                    AI(float64) = ai_last;
                }
            }
        }
        NEXT
    }
    BN_END_ALLOW_THREADS
    return y;
}

#line 344
NRA(push, float32) {
    npy_intp index;
    npy_float32 ai, ai_last, n_float;
    PyObject *y = PyArray_Copy(a);
    iter it;
    init_iter_one(&it, (PyArrayObject *)y, axis);
    if (LENGTH == 0 || NDIM == 0) {
        return y;
    }
    n_float = n < 0 ? BN_INFINITY : (npy_float32)n;
    BN_BEGIN_ALLOW_THREADS
    WHILE {
        index = 0;
        ai_last = BN_NAN;
        FOR {
            ai = AI(float32);
            if (ai == ai) {
                ai_last = ai;
                index = INDEX;
            } else {
                if (INDEX - index <= n_float) {
                    AI(float32) = ai_last;
                }
            }
        }
        NEXT
    }
    BN_END_ALLOW_THREADS
    return y;
}

#line 377
NRA(push, int64) {
    PyObject *y = PyArray_Copy(a);
    return y;
}

#line 377
NRA(push, int32) {
    PyObject *y = PyArray_Copy(a);
    return y;
}

NRA_MAIN(push, PARSE_PUSH)

/* python strings -------------------------------------------------------- */

PyObject *pystr_a = NULL;
PyObject *pystr_n = NULL;
PyObject *pystr_kth = NULL;
PyObject *pystr_axis = NULL;

#line 393
static int
intern_strings(void) {
    pystr_a = PyUnicode_InternFromString("a");
    pystr_n = PyUnicode_InternFromString("n");
    pystr_kth = PyUnicode_InternFromString("kth");
    pystr_axis = PyUnicode_InternFromString("axis");
    return pystr_a && pystr_n && pystr_axis;
}

/* nonreducer_axis ------------------------------------------------------- */

static inline int
parse_partition(PyObject *args,
                PyObject *kwds,
                PyObject **a,
                PyObject **n,
                PyObject **axis) {
    const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
    const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_Size(kwds);
    if (nkwds) {
        int nkwds_found = 0;
        PyObject *tmp;
        switch (nargs) {
            case 2: *n = PyTuple_GET_ITEM(args, 1);
            case 1: *a = PyTuple_GET_ITEM(args, 0);
            case 0: break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
        switch (nargs) {
            case 0:
                *a = PyDict_GetItem(kwds, pystr_a);
                if (*a == NULL) {
                    TYPE_ERR("Cannot find `a` keyword input");
                    return 0;
                }
                nkwds_found += 1;
            case 1:
                *n = PyDict_GetItem(kwds, pystr_kth);
                if (*n == NULL) {
                    TYPE_ERR("Cannot find `kth` keyword input");
                    return 0;
                }
                nkwds_found++;
            case 2:
                tmp = PyDict_GetItem(kwds, pystr_axis);
                if (tmp != NULL) {
                    *axis = tmp;
                    nkwds_found++;
                }
                break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
        if (nkwds_found != nkwds) {
            TYPE_ERR("wrong number of keyword arguments");
            return 0;
        }
        if (nargs + nkwds_found > 3) {
            TYPE_ERR("too many arguments");
            return 0;
        }
    } else {
        switch (nargs) {
            case 3:
                *axis = PyTuple_GET_ITEM(args, 2);
            case 2:
                *n = PyTuple_GET_ITEM(args, 1);
                *a = PyTuple_GET_ITEM(args, 0);
                break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
    }

    return 1;

}

static inline int
parse_rankdata(PyObject *args,
               PyObject *kwds,
               PyObject **a,
               PyObject **axis) {
    const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
    const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_Size(kwds);
    if (nkwds) {
        int nkwds_found = 0;
        PyObject *tmp;
        switch (nargs) {
            case 1: *a = PyTuple_GET_ITEM(args, 0);
            case 0: break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
        switch (nargs) {
            case 0:
                *a = PyDict_GetItem(kwds, pystr_a);
                if (*a == NULL) {
                    TYPE_ERR("Cannot find `a` keyword input");
                    return 0;
                }
                nkwds_found += 1;
            case 1:
                tmp = PyDict_GetItem(kwds, pystr_axis);
                if (tmp != NULL) {
                    *axis = tmp;
                    nkwds_found++;
                }
                break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
        if (nkwds_found != nkwds) {
            TYPE_ERR("wrong number of keyword arguments");
            return 0;
        }
        if (nargs + nkwds_found > 2) {
            TYPE_ERR("too many arguments");
            return 0;
        }
    } else {
        switch (nargs) {
            case 2:
                *axis = PyTuple_GET_ITEM(args, 1);
            case 1:
                *a = PyTuple_GET_ITEM(args, 0);
                break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
    }

    return 1;

}

static inline int
parse_push(PyObject *args,
           PyObject *kwds,
           PyObject **a,
           PyObject **n,
           PyObject **axis) {
    const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
    const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_Size(kwds);
    if (nkwds) {
        int nkwds_found = 0;
        PyObject *tmp;
        switch (nargs) {
            case 2: *n = PyTuple_GET_ITEM(args, 1);
            case 1: *a = PyTuple_GET_ITEM(args, 0);
            case 0: break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
        switch (nargs) {
            case 0:
                *a = PyDict_GetItem(kwds, pystr_a);
                if (*a == NULL) {
                    TYPE_ERR("Cannot find `a` keyword input");
                    return 0;
                }
                nkwds_found += 1;
            case 1:
                tmp = PyDict_GetItem(kwds, pystr_n);
                if (tmp != NULL) {
                    *n = tmp;
                    nkwds_found++;
                }
            case 2:
                tmp = PyDict_GetItem(kwds, pystr_axis);
                if (tmp != NULL) {
                    *axis = tmp;
                    nkwds_found++;
                }
                break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
        if (nkwds_found != nkwds) {
            TYPE_ERR("wrong number of keyword arguments");
            return 0;
        }
        if (nargs + nkwds_found > 3) {
            TYPE_ERR("too many arguments");
            return 0;
        }
    } else {
        switch (nargs) {
            case 3:
                *axis = PyTuple_GET_ITEM(args, 2);
            case 2:
                *n = PyTuple_GET_ITEM(args, 1);
            case 1:
                *a = PyTuple_GET_ITEM(args, 0);
                break;
            default:
                TYPE_ERR("wrong number of arguments");
                return 0;
        }
    }

    return 1;

}

static PyObject *
nonreducer_axis(char *name,
                PyObject *args,
                PyObject *kwds,
                nra_t nra_float64,
                nra_t nra_float32,
                nra_t nra_int64,
                nra_t nra_int32,
                parse_type parse) {

    int n;
    int axis;
    int dtype;

    PyArrayObject *a;
    PyObject *y;

    PyObject *a_obj = NULL;
    PyObject *n_obj = NULL;
    PyObject *axis_obj = NULL;

    if (parse == PARSE_PARTITION) {
        if (!parse_partition(args, kwds, &a_obj, &n_obj, &axis_obj)) {
            return NULL;
        }
    } else if (parse == PARSE_RANKDATA) {
        if (!parse_rankdata(args, kwds, &a_obj, &axis_obj)) {
            return NULL;
        }
    } else if (parse == PARSE_PUSH) {
        if (!parse_push(args, kwds, &a_obj, &n_obj, &axis_obj)) {
            return NULL;
        }
    } else {
        RUNTIME_ERR("Unknown parse type; please report error.");
    }

    /* convert to array if necessary */
    if (PyArray_Check(a_obj)) {
        a = (PyArrayObject *)a_obj;
        Py_INCREF(a);
    } else {
        a = (PyArrayObject *)PyArray_FROM_O(a_obj);
        if (a == NULL) {
            return NULL;
        }
    }

    /* check for byte swapped input array */
    if (PyArray_ISBYTESWAPPED(a)) {
        Py_DECREF(a);
        return slow(name, args, kwds);
    }

    /* defend against the axis of negativity */
    if (axis_obj == NULL) {
        if (parse == PARSE_PARTITION || parse == PARSE_PUSH) {
            axis = PyArray_NDIM(a) - 1;
            if (axis < 0) {
                PyErr_Format(PyExc_ValueError,
                             "axis(=%d) out of bounds", axis);
                goto error;
            }
        } else {
            if (PyArray_NDIM(a) != 1) {
                a = (PyArrayObject *)PyArray_Ravel(a, NPY_CORDER);
            }
            axis = 0;
        }
    } else if (axis_obj == Py_None) {
        if (parse == PARSE_PUSH) {
            VALUE_ERR("`axis` cannot be None");
            goto error;
        }
        if (PyArray_NDIM(a) != 1) {
            a = (PyArrayObject *)PyArray_Ravel(a, NPY_CORDER);
        }
        axis = 0;
    } else {
        axis = PyArray_PyIntAsInt(axis_obj);
        if (error_converting(axis)) {
            TYPE_ERR("`axis` must be an integer");
            goto error;
        }
        if (axis < 0) {
            axis += PyArray_NDIM(a);
            if (axis < 0) {
                PyErr_Format(PyExc_ValueError,
                             "axis(=%d) out of bounds", axis);
                goto error;
            }
        } else if (axis >= PyArray_NDIM(a)) {
            PyErr_Format(PyExc_ValueError, "axis(=%d) out of bounds", axis);
            goto error;
        }
    }

    /* n */
    if (n_obj == NULL) {
        n = -1;
    } else if (parse == PARSE_PUSH && n_obj == Py_None) {
        n = -1;
    } else {
        n = PyArray_PyIntAsInt(n_obj);
        if (error_converting(n)) {
            TYPE_ERR("`n` must be an integer");
            goto error;
        }
        if (n < 0 && parse == PARSE_PUSH) {
            VALUE_ERR("`n` must be nonnegative");
            goto error;
        }
    }

    dtype = PyArray_TYPE(a);
    if      (dtype == NPY_float64) y = nra_float64(a, axis, n);
    else if (dtype == NPY_float32) y = nra_float32(a, axis, n);
    else if (dtype == NPY_int64)   y = nra_int64(a, axis, n);
    else if (dtype == NPY_int32)   y = nra_int32(a, axis, n);
    else                           y = slow(name, args, kwds);

    Py_DECREF(a);

    return y;

error:
    Py_DECREF(a);
    return NULL;

}

/* docstrings ------------------------------------------------------------- */

static char nra_doc[] =
"Bottleneck non-reducing functions that operate along an axis.";

static char partition_doc[] =
"partition(a, kth, axis=-1)\n"
"\n"
"Partition array elements along given axis.\n"
"\n"
"A 1d array B is partitioned at array index `kth` if three conditions\n"
"are met: (1) B[kth] is in its sorted position, (2) all elements to the\n"
"left of `kth` are less than or equal to B[kth], and (3) all elements\n"
"to the right of `kth` are greater than or equal to B[kth]. Note that\n"
"the array elements in conditions (2) and (3) are in general unordered.\n"
"\n"
"Shuffling the input array may change the output. The only guarantee is\n"
"given by the three conditions above.\n"
"\n"
"This functions is not protected against NaN. Therefore, you may get\n"
"unexpected results if the input contains NaN.\n"
"\n"
"Parameters\n"
"----------\n"
"a : array_like\n"
"    Input array. If `a` is not an array, a conversion is attempted.\n"
"kth : int\n"
"    The value of the element at index `kth` will be in its sorted\n"
"    position. Smaller (larger) or equal values will be to the left\n"
"    (right) of index `kth`.\n"
"axis : {int, None}, optional\n"
"    Axis along which the partition is performed. The default\n"
"    (axis=-1) is to partition along the last axis.\n"
"\n"
"Returns\n"
"-------\n"
"y : ndarray\n"
"    A partitioned copy of the input array with the same shape and\n"
"    type of `a`.\n"
"\n"
"See Also\n"
"--------\n"
"bottleneck.argpartition: Indices that would partition an array\n"
"\n"
"Notes\n"
"-----\n"
"Unexpected results may occur if the input array contains NaN.\n"
"\n"
"Examples\n"
"--------\n"
"Create a numpy array:\n"
"\n"
">>> a = np.array([1, 0, 3, 4, 2])\n"
"\n"
"Partition array so that the first 3 elements (indices 0, 1, 2) are the\n"
"smallest 3 elements (note, as in this example, that the smallest 3\n"
"elements may not be sorted):\n"
"\n"
">>> bn.partition(a, kth=2)\n"
"array([1, 0, 2, 4, 3])\n"
"\n"
"Now Partition array so that the last 2 elements are the largest 2\n"
"elements:\n"
"\n"
">>> bn.partition(a, kth=3)\n"
"array([1, 0, 2, 3, 4])\n"
"\n";

static char argpartition_doc[] =
"argpartition(a, kth, axis=-1)\n"
"\n"
"Return indices that would partition array along the given axis.\n"
"\n"
"A 1d array B is partitioned at array index `kth` if three conditions\n"
"are met: (1) B[kth] is in its sorted position, (2) all elements to the\n"
"left of `kth` are less than or equal to B[kth], and (3) all elements\n"
"to the right of `kth` are greater than or equal to B[kth]. Note that\n"
"the array elements in conditions (2) and (3) are in general unordered.\n"
"\n"
"Shuffling the input array may change the output. The only guarantee is\n"
"given by the three conditions above.\n"
"\n"
"This functions is not protected against NaN. Therefore, you may get\n"
"unexpected results if the input contains NaN.\n"
"\n"
"Parameters\n"
"----------\n"
"a : array_like\n"
"    Input array. If `a` is not an array, a conversion is attempted.\n"
"kth : int\n"
"    The value of the element at index `kth` will be in its sorted\n"
"    position. Smaller (larger) or equal values will be to the left\n"
"    (right) of index `kth`.\n"
"axis : {int, None}, optional\n"
"    Axis along which the partition is performed. The default (axis=-1)\n"
"    is to partition along the last axis.\n"
"\n"
"Returns\n"
"-------\n"
"y : ndarray\n"
"    An array the same shape as the input array containing the indices\n"
"    that partition `a`. The dtype of the indices is numpy.intp.\n"
"\n"
"See Also\n"
"--------\n"
"bottleneck.partition: Partition array elements along given axis.\n"
"\n"
"Notes\n"
"-----\n"
"Unexpected results may occur if the input array contains NaN.\n"
"\n"
"Examples\n"
"--------\n"
"Create a numpy array:\n"
"\n"
">>> a = np.array([10, 0, 30, 40, 20])\n"
"\n"
"Find the indices that partition the array so that the first 3\n"
"elements are the smallest 3 elements:\n"
"\n"
">>> index = bn.argpartition(a, kth=2)\n"
">>> index\n"
"array([0, 1, 4, 3, 2])\n"
"\n"
"Let's use the indices to partition the array (note, as in this\n"
"example, that the smallest 3 elements may not be in order):\n"
"\n"
">>> a[index]\n"
"array([10, 0, 20, 40, 30])\n"
"\n";

static char rankdata_doc[] =
"rankdata(a, axis=None)\n"
"\n"
"Ranks the data, dealing with ties appropriately.\n"
"\n"
"Equal values are assigned a rank that is the average of the ranks that\n"
"would have been otherwise assigned to all of the values within that set.\n"
"Ranks begin at 1, not 0.\n"
"\n"
"Parameters\n"
"----------\n"
"a : array_like\n"
"    Input array. If `a` is not an array, a conversion is attempted.\n"
"axis : {int, None}, optional\n"
"    Axis along which the elements of the array are ranked. The default\n"
"    (axis=None) is to rank the elements of the flattened array.\n"
"\n"
"Returns\n"
"-------\n"
"y : ndarray\n"
"    An array with the same shape as `a`. The dtype is 'float64'.\n"
"\n"
"See also\n"
"--------\n"
"bottleneck.nanrankdata: Ranks the data dealing with ties and NaNs.\n"
"\n"
"Examples\n"
"--------\n"
">>> bn.rankdata([0, 2, 2, 3])\n"
"array([ 1. ,  2.5,  2.5,  4. ])\n"
">>> bn.rankdata([[0, 2], [2, 3]])\n"
"array([ 1. ,  2.5,  2.5,  4. ])\n"
">>> bn.rankdata([[0, 2], [2, 3]], axis=0)\n"
"array([[ 1.,  1.],\n"
"       [ 2.,  2.]])\n"
">>> bn.rankdata([[0, 2], [2, 3]], axis=1)\n"
"array([[ 1.,  2.],\n"
"       [ 1.,  2.]])\n"
"\n";

static char nanrankdata_doc[] =
"nanrankdata(a, axis=None)\n"
"\n"
"Ranks the data, dealing with ties and NaNs appropriately.\n"
"\n"
"Equal values are assigned a rank that is the average of the ranks that\n"
"would have been otherwise assigned to all of the values within that set.\n"
"Ranks begin at 1, not 0.\n"
"\n"
"NaNs in the input array are returned as NaNs.\n"
"\n"
"Parameters\n"
"----------\n"
"a : array_like\n"
"    Input array. If `a` is not an array, a conversion is attempted.\n"
"axis : {int, None}, optional\n"
"    Axis along which the elements of the array are ranked. The default\n"
"    (axis=None) is to rank the elements of the flattened array.\n"
"\n"
"Returns\n"
"-------\n"
"y : ndarray\n"
"    An array with the same shape as `a`. The dtype is 'float64'.\n"
"\n"
"See also\n"
"--------\n"
"bottleneck.rankdata: Ranks the data, dealing with ties and appropriately.\n"
"\n"
"Examples\n"
"--------\n"
">>> bn.nanrankdata([np.nan, 2, 2, 3])\n"
"array([ nan,  1.5,  1.5,  3. ])\n"
">>> bn.nanrankdata([[np.nan, 2], [2, 3]])\n"
"array([ nan,  1.5,  1.5,  3. ])\n"
">>> bn.nanrankdata([[np.nan, 2], [2, 3]], axis=0)\n"
"array([[ nan,   1.],\n"
"       [  1.,   2.]])\n"
">>> bn.nanrankdata([[np.nan, 2], [2, 3]], axis=1)\n"
"array([[ nan,   1.],\n"
"       [  1.,   2.]])\n"
"\n";

static char push_doc[] =
"push(a, n=None, axis=-1)\n"
"\n"
"Fill missing values (NaNs) with most recent non-missing values.\n"
"\n"
"Filling proceeds along the specified axis from small index values to large\n"
"index values.\n"
"\n"
"Parameters\n"
"----------\n"
"a : array_like\n"
"    Input array. If `a` is not an array, a conversion is attempted.\n"
"n : {int, None}, optional\n"
"    How far to push values. If the most recent non-NaN array element is\n"
"    more than `n` index positions away, than a NaN is returned. The default\n"
"    (n = None) is to push the entire length of the slice. If `n` is an integer\n"
"    it must be nonnegative.\n"
"axis : int, optional\n"
"    Axis along which the elements of the array are pushed. The default\n"
"    (axis=-1) is to push along the last axis of the input array.\n"
"\n"
"Returns\n"
"-------\n"
"y : ndarray\n"
"    An array with the same shape and dtype as `a`.\n"
"\n"
"See also\n"
"--------\n"
"bottleneck.replace: Replace specified value of an array with new value.\n"
"\n"
"Examples\n"
"--------\n"
">>> a = np.array([5, np.nan, np.nan, 6, np.nan])\n"
">>> bn.push(a)\n"
"    array([ 5.,  5.,  5.,  6.,  6.])\n"
">>> bn.push(a, n=1)\n"
"    array([  5.,   5.,  nan,   6.,   6.])\n"
">>> bn.push(a, n=2)\n"
"    array([ 5.,  5.,  5.,  6.,  6.])\n"
"\n";

/* python wrapper -------------------------------------------------------- */

#line 1004
static PyMethodDef
nra_methods[] = {
    {"partition",    (PyCFunction)partition,    VARKEY, partition_doc},
    {"argpartition", (PyCFunction)argpartition, VARKEY, argpartition_doc},
    {"rankdata",     (PyCFunction)rankdata,     VARKEY, rankdata_doc},
    {"nanrankdata",  (PyCFunction)nanrankdata,  VARKEY, nanrankdata_doc},
    {"push",         (PyCFunction)push,         VARKEY, push_doc},
    {NULL, NULL, 0, NULL}
};

#line 1015
static struct PyModuleDef
nra_def = {
   PyModuleDef_HEAD_INIT,
   "nonreduce_axis",
   nra_doc,
   -1,
   nra_methods
};

#line 1025
PyMODINIT_FUNC
PyInit_nonreduce_axis(void)
{
    PyObject *m = PyModule_Create(&nra_def);
    if (m == NULL) return NULL;

    #ifdef Py_GIL_DISABLED
        PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
    #endif

    import_array();
    if (!intern_strings()) {
        return NULL;
    }
    return m;
}
