#line 1 "numpy/linalg/umath_linalg.c.src"

/*
 *****************************************************************************
 **       This file was autogenerated from a template  DO NOT EDIT!!!!      **
 **       Changes should be made to the original source (.src) file         **
 *****************************************************************************
 */

#line 1
/* -*- c -*- */

/*
 *****************************************************************************
 **                            INCLUDES                                     **
 *****************************************************************************
 */
#define NPY_NO_DEPRECATED_API NPY_API_VERSION

#include "Python.h"
#include "numpy/arrayobject.h"
#include "numpy/ufuncobject.h"

#include "npy_pycompat.h"

#include "npy_config.h"

#include "npy_cblas.h"

#include <stddef.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>


static const char* umath_linalg_version_string = "0.1.5";

/*
 ****************************************************************************
 *                        Debugging support                                 *
 ****************************************************************************
 */
#define TRACE_TXT(...) do { fprintf (stderr, __VA_ARGS__); } while (0)
#define STACK_TRACE do {} while (0)
#define TRACE\
    do {                                        \
        fprintf (stderr,                        \
                 "%s:%d:%s\n",                  \
                 __FILE__,                      \
                 __LINE__,                      \
                 __FUNCTION__);                 \
        STACK_TRACE;                            \
    } while (0)

#if 0
#include <execinfo.h>
void
dbg_stack_trace()
{
    void *trace[32];
    size_t size;

    size = backtrace(trace, sizeof(trace)/sizeof(trace[0]));
    backtrace_symbols_fd(trace, size, 1);
}

#undef STACK_TRACE
#define STACK_TRACE do { dbg_stack_trace(); } while (0)
#endif

/*
 *****************************************************************************
 *                    BLAS/LAPACK calling macros                             *
 *****************************************************************************
 */

#define FNAME(x) BLAS_FUNC(x)

typedef CBLAS_INT         fortran_int;

typedef struct { float r, i; } f2c_complex;
typedef struct { double r, i; } f2c_doublecomplex;
/* typedef long int (*L_fp)(); */

typedef float             fortran_real;
typedef double            fortran_doublereal;
typedef f2c_complex       fortran_complex;
typedef f2c_doublecomplex fortran_doublecomplex;

extern fortran_int
FNAME(sgeev)(char *jobvl, char *jobvr, fortran_int *n,
             float a[], fortran_int *lda, float wr[], float wi[],
             float vl[], fortran_int *ldvl, float vr[], fortran_int *ldvr,
             float work[], fortran_int lwork[],
             fortran_int *info);
extern fortran_int
FNAME(dgeev)(char *jobvl, char *jobvr, fortran_int *n,
             double a[], fortran_int *lda, double wr[], double wi[],
             double vl[], fortran_int *ldvl, double vr[], fortran_int *ldvr,
             double work[], fortran_int lwork[],
             fortran_int *info);
extern fortran_int
FNAME(cgeev)(char *jobvl, char *jobvr, fortran_int *n,
             f2c_doublecomplex a[], fortran_int *lda,
             f2c_doublecomplex w[],
             f2c_doublecomplex vl[], fortran_int *ldvl,
             f2c_doublecomplex vr[], fortran_int *ldvr,
             f2c_doublecomplex work[], fortran_int *lwork,
             double rwork[],
             fortran_int *info);
extern fortran_int
FNAME(zgeev)(char *jobvl, char *jobvr, fortran_int *n,
             f2c_doublecomplex a[], fortran_int *lda,
             f2c_doublecomplex w[],
             f2c_doublecomplex vl[], fortran_int *ldvl,
             f2c_doublecomplex vr[], fortran_int *ldvr,
             f2c_doublecomplex work[], fortran_int *lwork,
             double rwork[],
             fortran_int *info);

extern fortran_int
FNAME(ssyevd)(char *jobz, char *uplo, fortran_int *n,
              float a[], fortran_int *lda, float w[], float work[],
              fortran_int *lwork, fortran_int iwork[], fortran_int *liwork,
              fortran_int *info);
extern fortran_int
FNAME(dsyevd)(char *jobz, char *uplo, fortran_int *n,
              double a[], fortran_int *lda, double w[], double work[],
              fortran_int *lwork, fortran_int iwork[], fortran_int *liwork,
              fortran_int *info);
extern fortran_int
FNAME(cheevd)(char *jobz, char *uplo, fortran_int *n,
              f2c_complex a[], fortran_int *lda,
              float w[], f2c_complex work[],
              fortran_int *lwork, float rwork[], fortran_int *lrwork, fortran_int iwork[],
              fortran_int *liwork,
              fortran_int *info);
extern fortran_int
FNAME(zheevd)(char *jobz, char *uplo, fortran_int *n,
              f2c_doublecomplex a[], fortran_int *lda,
              double w[], f2c_doublecomplex work[],
              fortran_int *lwork, double rwork[], fortran_int *lrwork, fortran_int iwork[],
              fortran_int *liwork,
              fortran_int *info);

extern fortran_int
FNAME(sgelsd)(fortran_int *m, fortran_int *n, fortran_int *nrhs,
              float a[], fortran_int *lda, float b[], fortran_int *ldb,
              float s[], float *rcond, fortran_int *rank,
              float work[], fortran_int *lwork, fortran_int iwork[],
              fortran_int *info);
extern fortran_int
FNAME(dgelsd)(fortran_int *m, fortran_int *n, fortran_int *nrhs,
              double a[], fortran_int *lda, double b[], fortran_int *ldb,
              double s[], double *rcond, fortran_int *rank,
              double work[], fortran_int *lwork, fortran_int iwork[],
              fortran_int *info);
extern fortran_int
FNAME(cgelsd)(fortran_int *m, fortran_int *n, fortran_int *nrhs,
              f2c_complex a[], fortran_int *lda,
              f2c_complex b[], fortran_int *ldb,
              float s[], float *rcond, fortran_int *rank,
              f2c_complex work[], fortran_int *lwork,
              float rwork[], fortran_int iwork[],
              fortran_int *info);
extern fortran_int
FNAME(zgelsd)(fortran_int *m, fortran_int *n, fortran_int *nrhs,
              f2c_doublecomplex a[], fortran_int *lda,
              f2c_doublecomplex b[], fortran_int *ldb,
              double s[], double *rcond, fortran_int *rank,
              f2c_doublecomplex work[], fortran_int *lwork,
              double rwork[], fortran_int iwork[],
              fortran_int *info);

extern fortran_int
FNAME(sgesv)(fortran_int *n, fortran_int *nrhs,
             float a[], fortran_int *lda,
             fortran_int ipiv[],
             float b[], fortran_int *ldb,
             fortran_int *info);
extern fortran_int
FNAME(dgesv)(fortran_int *n, fortran_int *nrhs,
             double a[], fortran_int *lda,
             fortran_int ipiv[],
             double b[], fortran_int *ldb,
             fortran_int *info);
extern fortran_int
FNAME(cgesv)(fortran_int *n, fortran_int *nrhs,
             f2c_complex a[], fortran_int *lda,
             fortran_int ipiv[],
             f2c_complex b[], fortran_int *ldb,
             fortran_int *info);
extern fortran_int
FNAME(zgesv)(fortran_int *n, fortran_int *nrhs,
             f2c_doublecomplex a[], fortran_int *lda,
             fortran_int ipiv[],
             f2c_doublecomplex b[], fortran_int *ldb,
             fortran_int *info);

extern fortran_int
FNAME(sgetrf)(fortran_int *m, fortran_int *n,
              float a[], fortran_int *lda,
              fortran_int ipiv[],
              fortran_int *info);
extern fortran_int
FNAME(dgetrf)(fortran_int *m, fortran_int *n,
              double a[], fortran_int *lda,
              fortran_int ipiv[],
              fortran_int *info);
extern fortran_int
FNAME(cgetrf)(fortran_int *m, fortran_int *n,
              f2c_complex a[], fortran_int *lda,
              fortran_int ipiv[],
              fortran_int *info);
extern fortran_int
FNAME(zgetrf)(fortran_int *m, fortran_int *n,
              f2c_doublecomplex a[], fortran_int *lda,
              fortran_int ipiv[],
              fortran_int *info);

extern fortran_int
FNAME(spotrf)(char *uplo, fortran_int *n,
              float a[], fortran_int *lda,
              fortran_int *info);
extern fortran_int
FNAME(dpotrf)(char *uplo, fortran_int *n,
              double a[], fortran_int *lda,
              fortran_int *info);
extern fortran_int
FNAME(cpotrf)(char *uplo, fortran_int *n,
              f2c_complex a[], fortran_int *lda,
              fortran_int *info);
extern fortran_int
FNAME(zpotrf)(char *uplo, fortran_int *n,
              f2c_doublecomplex a[], fortran_int *lda,
              fortran_int *info);

extern fortran_int
FNAME(sgesdd)(char *jobz, fortran_int *m, fortran_int *n,
              float a[], fortran_int *lda, float s[], float u[],
              fortran_int *ldu, float vt[], fortran_int *ldvt, float work[],
              fortran_int *lwork, fortran_int iwork[], fortran_int *info);
extern fortran_int
FNAME(dgesdd)(char *jobz, fortran_int *m, fortran_int *n,
              double a[], fortran_int *lda, double s[], double u[],
              fortran_int *ldu, double vt[], fortran_int *ldvt, double work[],
              fortran_int *lwork, fortran_int iwork[], fortran_int *info);
extern fortran_int
FNAME(cgesdd)(char *jobz, fortran_int *m, fortran_int *n,
              f2c_complex a[], fortran_int *lda,
              float s[], f2c_complex u[], fortran_int *ldu,
              f2c_complex vt[], fortran_int *ldvt,
              f2c_complex work[], fortran_int *lwork,
              float rwork[], fortran_int iwork[], fortran_int *info);
extern fortran_int
FNAME(zgesdd)(char *jobz, fortran_int *m, fortran_int *n,
              f2c_doublecomplex a[], fortran_int *lda,
              double s[], f2c_doublecomplex u[], fortran_int *ldu,
              f2c_doublecomplex vt[], fortran_int *ldvt,
              f2c_doublecomplex work[], fortran_int *lwork,
              double rwork[], fortran_int iwork[], fortran_int *info);

extern fortran_int
FNAME(spotrs)(char *uplo, fortran_int *n, fortran_int *nrhs,
              float a[], fortran_int *lda,
              float b[], fortran_int *ldb,
              fortran_int *info);
extern fortran_int
FNAME(dpotrs)(char *uplo, fortran_int *n, fortran_int *nrhs,
              double a[], fortran_int *lda,
              double b[], fortran_int *ldb,
              fortran_int *info);
extern fortran_int
FNAME(cpotrs)(char *uplo, fortran_int *n, fortran_int *nrhs,
              f2c_complex a[], fortran_int *lda,
              f2c_complex b[], fortran_int *ldb,
              fortran_int *info);
extern fortran_int
FNAME(zpotrs)(char *uplo, fortran_int *n, fortran_int *nrhs,
              f2c_doublecomplex a[], fortran_int *lda,
              f2c_doublecomplex b[], fortran_int *ldb,
              fortran_int *info);

extern fortran_int
FNAME(spotri)(char *uplo, fortran_int *n,
              float a[], fortran_int *lda,
              fortran_int *info);
extern fortran_int
FNAME(dpotri)(char *uplo, fortran_int *n,
              double a[], fortran_int *lda,
              fortran_int *info);
extern fortran_int
FNAME(cpotri)(char *uplo, fortran_int *n,
              f2c_complex a[], fortran_int *lda,
              fortran_int *info);
extern fortran_int
FNAME(zpotri)(char *uplo, fortran_int *n,
              f2c_doublecomplex a[], fortran_int *lda,
              fortran_int *info);

extern fortran_int
FNAME(scopy)(fortran_int *n,
             float *sx, fortran_int *incx,
             float *sy, fortran_int *incy);
extern fortran_int
FNAME(dcopy)(fortran_int *n,
             double *sx, fortran_int *incx,
             double *sy, fortran_int *incy);
extern fortran_int
FNAME(ccopy)(fortran_int *n,
             f2c_complex *sx, fortran_int *incx,
             f2c_complex *sy, fortran_int *incy);
extern fortran_int
FNAME(zcopy)(fortran_int *n,
             f2c_doublecomplex *sx, fortran_int *incx,
             f2c_doublecomplex *sy, fortran_int *incy);

extern float
FNAME(sdot)(fortran_int *n,
            float *sx, fortran_int *incx,
            float *sy, fortran_int *incy);
extern double
FNAME(ddot)(fortran_int *n,
            double *sx, fortran_int *incx,
            double *sy, fortran_int *incy);
extern void
FNAME(cdotu)(f2c_complex *ret, fortran_int *n,
             f2c_complex *sx, fortran_int *incx,
             f2c_complex *sy, fortran_int *incy);
extern void
FNAME(zdotu)(f2c_doublecomplex *ret, fortran_int *n,
             f2c_doublecomplex *sx, fortran_int *incx,
             f2c_doublecomplex *sy, fortran_int *incy);
extern void
FNAME(cdotc)(f2c_complex *ret, fortran_int *n,
             f2c_complex *sx, fortran_int *incx,
             f2c_complex *sy, fortran_int *incy);
extern void
FNAME(zdotc)(f2c_doublecomplex *ret, fortran_int *n,
             f2c_doublecomplex *sx, fortran_int *incx,
             f2c_doublecomplex *sy, fortran_int *incy);

extern fortran_int
FNAME(sgemm)(char *transa, char *transb,
             fortran_int *m, fortran_int *n, fortran_int *k,
             float *alpha,
             float *a, fortran_int *lda,
             float *b, fortran_int *ldb,
             float *beta,
             float *c, fortran_int *ldc);
extern fortran_int
FNAME(dgemm)(char *transa, char *transb,
             fortran_int *m, fortran_int *n, fortran_int *k,
             double *alpha,
             double *a, fortran_int *lda,
             double *b, fortran_int *ldb,
             double *beta,
             double *c, fortran_int *ldc);
extern fortran_int
FNAME(cgemm)(char *transa, char *transb,
             fortran_int *m, fortran_int *n, fortran_int *k,
             f2c_complex *alpha,
             f2c_complex *a, fortran_int *lda,
             f2c_complex *b, fortran_int *ldb,
             f2c_complex *beta,
             f2c_complex *c, fortran_int *ldc);
extern fortran_int
FNAME(zgemm)(char *transa, char *transb,
             fortran_int *m, fortran_int *n, fortran_int *k,
             f2c_doublecomplex *alpha,
             f2c_doublecomplex *a, fortran_int *lda,
             f2c_doublecomplex *b, fortran_int *ldb,
             f2c_doublecomplex *beta,
             f2c_doublecomplex *c, fortran_int *ldc);


#define LAPACK_T(FUNC)                                          \
    TRACE_TXT("Calling LAPACK ( " # FUNC " )\n");               \
    FNAME(FUNC)

#define BLAS(FUNC)                              \
    FNAME(FUNC)

#define LAPACK(FUNC)                            \
    FNAME(FUNC)


/*
 *****************************************************************************
 **                      Some handy functions                               **
 *****************************************************************************
 */

static NPY_INLINE int
get_fp_invalid_and_clear(void)
{
    int status;
    status = npy_clear_floatstatus_barrier((char*)&status);
    return !!(status & NPY_FPE_INVALID);
}

static NPY_INLINE void
set_fp_invalid_or_clear(int error_occurred)
{
    if (error_occurred) {
        npy_set_floatstatus_invalid();
    }
    else {
        npy_clear_floatstatus_barrier((char*)&error_occurred);
    }
}

/*
 *****************************************************************************
 **                      Some handy constants                               **
 *****************************************************************************
 */

#define UMATH_LINALG_MODULE_NAME "_umath_linalg"

typedef union {
    fortran_complex f;
    npy_cfloat npy;
    float array[2];
} COMPLEX_t;

typedef union {
    fortran_doublecomplex f;
    npy_cdouble npy;
    double array[2];
} DOUBLECOMPLEX_t;

static float s_one;
static float s_zero;
static float s_minus_one;
static float s_ninf;
static float s_nan;
static double d_one;
static double d_zero;
static double d_minus_one;
static double d_ninf;
static double d_nan;
static COMPLEX_t c_one;
static COMPLEX_t c_zero;
static COMPLEX_t c_minus_one;
static COMPLEX_t c_ninf;
static COMPLEX_t c_nan;
static DOUBLECOMPLEX_t z_one;
static DOUBLECOMPLEX_t z_zero;
static DOUBLECOMPLEX_t z_minus_one;
static DOUBLECOMPLEX_t z_ninf;
static DOUBLECOMPLEX_t z_nan;

static void init_constants(void)
{
    /*
       this is needed as NPY_INFINITY and NPY_NAN macros
       can't be used as initializers. I prefer to just set
       all the constants the same way.
    */
    s_one  = 1.0f;
    s_zero = 0.0f;
    s_minus_one = -1.0f;
    s_ninf = -NPY_INFINITYF;
    s_nan = NPY_NANF;

    d_one  = 1.0;
    d_zero = 0.0;
    d_minus_one = -1.0;
    d_ninf = -NPY_INFINITY;
    d_nan = NPY_NAN;

    c_one.array[0]  = 1.0f;
    c_one.array[1]  = 0.0f;
    c_zero.array[0] = 0.0f;
    c_zero.array[1] = 0.0f;
    c_minus_one.array[0] = -1.0f;
    c_minus_one.array[1] = 0.0f;
    c_ninf.array[0] = -NPY_INFINITYF;
    c_ninf.array[1] = 0.0f;
    c_nan.array[0] = NPY_NANF;
    c_nan.array[1] = NPY_NANF;

    z_one.array[0]  = 1.0;
    z_one.array[1]  = 0.0;
    z_zero.array[0] = 0.0;
    z_zero.array[1] = 0.0;
    z_minus_one.array[0] = -1.0;
    z_minus_one.array[1] = 0.0;
    z_ninf.array[0] = -NPY_INFINITY;
    z_ninf.array[1] = 0.0;
    z_nan.array[0] = NPY_NAN;
    z_nan.array[1] = NPY_NAN;
}

/*
 *****************************************************************************
 **               Structs used for data rearrangement                       **
 *****************************************************************************
 */


/*
 * this struct contains information about how to linearize a matrix in a local
 * buffer so that it can be used by blas functions.  All strides are specified
 * in bytes and are converted to elements later in type specific functions.
 *
 * rows: number of rows in the matrix
 * columns: number of columns in the matrix
 * row_strides: the number bytes between consecutive rows.
 * column_strides: the number of bytes between consecutive columns.
 * output_lead_dim: BLAS/LAPACK-side leading dimension, in elements
 */
typedef struct linearize_data_struct
{
  npy_intp rows;
  npy_intp columns;
  npy_intp row_strides;
  npy_intp column_strides;
  npy_intp output_lead_dim;
} LINEARIZE_DATA_t;

static NPY_INLINE void
init_linearize_data_ex(LINEARIZE_DATA_t *lin_data,
                       npy_intp rows,
                       npy_intp columns,
                       npy_intp row_strides,
                       npy_intp column_strides,
                       npy_intp output_lead_dim)
{
    lin_data->rows = rows;
    lin_data->columns = columns;
    lin_data->row_strides = row_strides;
    lin_data->column_strides = column_strides;
    lin_data->output_lead_dim = output_lead_dim;
}

static NPY_INLINE void
init_linearize_data(LINEARIZE_DATA_t *lin_data,
                    npy_intp rows,
                    npy_intp columns,
                    npy_intp row_strides,
                    npy_intp column_strides)
{
    init_linearize_data_ex(
        lin_data, rows, columns, row_strides, column_strides, columns);
}

static NPY_INLINE void
dump_ufunc_object(PyUFuncObject* ufunc)
{
    TRACE_TXT("\n\n%s '%s' (%d input(s), %d output(s), %d specialization(s).\n",
              ufunc->core_enabled? "generalized ufunc" : "scalar ufunc",
              ufunc->name, ufunc->nin, ufunc->nout, ufunc->ntypes);
    if (ufunc->core_enabled) {
        int arg;
        int dim;
        TRACE_TXT("\t%s (%d dimension(s) detected).\n",
                  ufunc->core_signature, ufunc->core_num_dim_ix);

        for (arg = 0; arg < ufunc->nargs; arg++){
            int * arg_dim_ix = ufunc->core_dim_ixs + ufunc->core_offsets[arg];
            TRACE_TXT("\t\targ %d (%s) has %d dimension(s): (",
                      arg, arg < ufunc->nin? "INPUT" : "OUTPUT",
                      ufunc->core_num_dims[arg]);
            for (dim = 0; dim < ufunc->core_num_dims[arg]; dim ++) {
                TRACE_TXT(" %d", arg_dim_ix[dim]);
            }
            TRACE_TXT(" )\n");
        }
    }
}

static NPY_INLINE void
dump_linearize_data(const char* name, const LINEARIZE_DATA_t* params)
{
    TRACE_TXT("\n\t%s rows: %zd columns: %zd"\
              "\n\t\trow_strides: %td column_strides: %td"\
              "\n", name, params->rows, params->columns,
              params->row_strides, params->column_strides);
}

static NPY_INLINE void
print_FLOAT(npy_float s)
{
    TRACE_TXT(" %8.4f", s);
}
static NPY_INLINE void
print_DOUBLE(npy_double d)
{
    TRACE_TXT(" %10.6f", d);
}
static NPY_INLINE void
print_CFLOAT(npy_cfloat c)
{
    float* c_parts = (float*)&c;
    TRACE_TXT("(%8.4f, %8.4fj)", c_parts[0], c_parts[1]);
}
static NPY_INLINE void
print_CDOUBLE(npy_cdouble z)
{
    double* z_parts = (double*)&z;
    TRACE_TXT("(%8.4f, %8.4fj)", z_parts[0], z_parts[1]);
}

#line 600
static NPY_INLINE void
dump_FLOAT_matrix(const char* name,
                   size_t rows, size_t columns,
                   const npy_float* ptr)
{
    size_t i, j;

    TRACE_TXT("\n%s %p (%zd, %zd)\n", name, ptr, rows, columns);
    for (i = 0; i < rows; i++)
    {
        TRACE_TXT("| ");
        for (j = 0; j < columns; j++)
        {
            print_FLOAT(ptr[j*rows + i]);
            TRACE_TXT(", ");
        }
        TRACE_TXT(" |\n");
    }
}

#line 600
static NPY_INLINE void
dump_DOUBLE_matrix(const char* name,
                   size_t rows, size_t columns,
                   const npy_double* ptr)
{
    size_t i, j;

    TRACE_TXT("\n%s %p (%zd, %zd)\n", name, ptr, rows, columns);
    for (i = 0; i < rows; i++)
    {
        TRACE_TXT("| ");
        for (j = 0; j < columns; j++)
        {
            print_DOUBLE(ptr[j*rows + i]);
            TRACE_TXT(", ");
        }
        TRACE_TXT(" |\n");
    }
}

#line 600
static NPY_INLINE void
dump_CFLOAT_matrix(const char* name,
                   size_t rows, size_t columns,
                   const npy_cfloat* ptr)
{
    size_t i, j;

    TRACE_TXT("\n%s %p (%zd, %zd)\n", name, ptr, rows, columns);
    for (i = 0; i < rows; i++)
    {
        TRACE_TXT("| ");
        for (j = 0; j < columns; j++)
        {
            print_CFLOAT(ptr[j*rows + i]);
            TRACE_TXT(", ");
        }
        TRACE_TXT(" |\n");
    }
}

#line 600
static NPY_INLINE void
dump_CDOUBLE_matrix(const char* name,
                   size_t rows, size_t columns,
                   const npy_cdouble* ptr)
{
    size_t i, j;

    TRACE_TXT("\n%s %p (%zd, %zd)\n", name, ptr, rows, columns);
    for (i = 0; i < rows; i++)
    {
        TRACE_TXT("| ");
        for (j = 0; j < columns; j++)
        {
            print_CDOUBLE(ptr[j*rows + i]);
            TRACE_TXT(", ");
        }
        TRACE_TXT(" |\n");
    }
}



/*
 *****************************************************************************
 **                            Basics                                       **
 *****************************************************************************
 */

static NPY_INLINE fortran_int
fortran_int_min(fortran_int x, fortran_int y) {
    return x < y ? x : y;
}

static NPY_INLINE fortran_int
fortran_int_max(fortran_int x, fortran_int y) {
    return x > y ? x : y;
}

#define INIT_OUTER_LOOP_1 \
    npy_intp dN = *dimensions++;\
    npy_intp N_;\
    npy_intp s0 = *steps++;

#define INIT_OUTER_LOOP_2 \
    INIT_OUTER_LOOP_1\
    npy_intp s1 = *steps++;

#define INIT_OUTER_LOOP_3 \
    INIT_OUTER_LOOP_2\
    npy_intp s2 = *steps++;

#define INIT_OUTER_LOOP_4 \
    INIT_OUTER_LOOP_3\
    npy_intp s3 = *steps++;

#define INIT_OUTER_LOOP_5 \
    INIT_OUTER_LOOP_4\
    npy_intp s4 = *steps++;

#define INIT_OUTER_LOOP_6  \
    INIT_OUTER_LOOP_5\
    npy_intp s5 = *steps++;

#define INIT_OUTER_LOOP_7  \
    INIT_OUTER_LOOP_6\
    npy_intp s6 = *steps++;

#define BEGIN_OUTER_LOOP_2 \
    for (N_ = 0;\
         N_ < dN;\
         N_++, args[0] += s0,\
             args[1] += s1) {

#define BEGIN_OUTER_LOOP_3 \
    for (N_ = 0;\
         N_ < dN;\
         N_++, args[0] += s0,\
             args[1] += s1,\
             args[2] += s2) {

#define BEGIN_OUTER_LOOP_4 \
    for (N_ = 0;\
         N_ < dN;\
         N_++, args[0] += s0,\
             args[1] += s1,\
             args[2] += s2,\
             args[3] += s3) {

#define BEGIN_OUTER_LOOP_5 \
    for (N_ = 0;\
         N_ < dN;\
         N_++, args[0] += s0,\
             args[1] += s1,\
             args[2] += s2,\
             args[3] += s3,\
             args[4] += s4) {

#define BEGIN_OUTER_LOOP_6 \
    for (N_ = 0;\
         N_ < dN;\
         N_++, args[0] += s0,\
             args[1] += s1,\
             args[2] += s2,\
             args[3] += s3,\
             args[4] += s4,\
             args[5] += s5) {

#define BEGIN_OUTER_LOOP_7 \
    for (N_ = 0;\
         N_ < dN;\
         N_++, args[0] += s0,\
             args[1] += s1,\
             args[2] += s2,\
             args[3] += s3,\
             args[4] += s4,\
             args[5] += s5,\
             args[6] += s6) {

#define END_OUTER_LOOP  }

static NPY_INLINE void
update_pointers(npy_uint8** bases, ptrdiff_t* offsets, size_t count)
{
    size_t i;
    for (i = 0; i < count; ++i) {
        bases[i] += offsets[i];
    }
}


/* disable -Wmaybe-uninitialized as there is some code that generate false
   positives with this warning
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"

/*
 *****************************************************************************
 **                             HELPER FUNCS                                **
 *****************************************************************************
 */

             /* rearranging of 2D matrices using blas */

#line 751
static NPY_INLINE void *
linearize_FLOAT_matrix(void *dst_in,
                        void *src_in,
                        const LINEARIZE_DATA_t* data)
{
    float *src = (float *) src_in;
    float *dst = (float *) dst_in;

    if (dst) {
        int i, j;
        float* rv = dst;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(float));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(scopy)(&columns,
                              (void*)src, &column_strides,
                              (void*)dst, &one);
            }
            else if (column_strides < 0) {
                FNAME(scopy)(&columns,
                              (void*)((float*)src + (columns-1)*column_strides),
                              &column_strides,
                              (void*)dst, &one);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                for (j = 0; j < columns; ++j) {
                    memcpy((float*)dst + j, (float*)src, sizeof(float));
                }
            }
            src += data->row_strides/sizeof(float);
            dst += data->output_lead_dim;
        }
        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void *
delinearize_FLOAT_matrix(void *dst_in,
                          void *src_in,
                          const LINEARIZE_DATA_t* data)
{
    float *src = (float *) src_in;
    float *dst = (float *) dst_in;

    if (src) {
        int i;
        float *rv = src;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(float));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(scopy)(&columns,
                              (void*)src, &one,
                              (void*)dst, &column_strides);
            }
            else if (column_strides < 0) {
                FNAME(scopy)(&columns,
                              (void*)src, &one,
                              (void*)((float*)dst + (columns-1)*column_strides),
                              &column_strides);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                if (columns > 0) {
                    memcpy((float*)dst,
                           (float*)src + (columns-1),
                           sizeof(float));
                }
            }
            src += data->output_lead_dim;
            dst += data->row_strides/sizeof(float);
        }

        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void
nan_FLOAT_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    float *dst = (float *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        float *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(float);
        for (j = 0; j < data->columns; ++j) {
            *cp = s_nan;
            cp += cs;
        }
        dst += data->row_strides/sizeof(float);
    }
}

static NPY_INLINE void
zero_FLOAT_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    float *dst = (float *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        float *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(float);
        for (j = 0; j < data->columns; ++j) {
            *cp = s_zero;
            cp += cs;
        }
        dst += data->row_strides/sizeof(float);
    }
}


#line 751
static NPY_INLINE void *
linearize_DOUBLE_matrix(void *dst_in,
                        void *src_in,
                        const LINEARIZE_DATA_t* data)
{
    double *src = (double *) src_in;
    double *dst = (double *) dst_in;

    if (dst) {
        int i, j;
        double* rv = dst;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(double));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(dcopy)(&columns,
                              (void*)src, &column_strides,
                              (void*)dst, &one);
            }
            else if (column_strides < 0) {
                FNAME(dcopy)(&columns,
                              (void*)((double*)src + (columns-1)*column_strides),
                              &column_strides,
                              (void*)dst, &one);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                for (j = 0; j < columns; ++j) {
                    memcpy((double*)dst + j, (double*)src, sizeof(double));
                }
            }
            src += data->row_strides/sizeof(double);
            dst += data->output_lead_dim;
        }
        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void *
delinearize_DOUBLE_matrix(void *dst_in,
                          void *src_in,
                          const LINEARIZE_DATA_t* data)
{
    double *src = (double *) src_in;
    double *dst = (double *) dst_in;

    if (src) {
        int i;
        double *rv = src;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(double));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(dcopy)(&columns,
                              (void*)src, &one,
                              (void*)dst, &column_strides);
            }
            else if (column_strides < 0) {
                FNAME(dcopy)(&columns,
                              (void*)src, &one,
                              (void*)((double*)dst + (columns-1)*column_strides),
                              &column_strides);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                if (columns > 0) {
                    memcpy((double*)dst,
                           (double*)src + (columns-1),
                           sizeof(double));
                }
            }
            src += data->output_lead_dim;
            dst += data->row_strides/sizeof(double);
        }

        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void
nan_DOUBLE_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    double *dst = (double *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        double *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(double);
        for (j = 0; j < data->columns; ++j) {
            *cp = d_nan;
            cp += cs;
        }
        dst += data->row_strides/sizeof(double);
    }
}

static NPY_INLINE void
zero_DOUBLE_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    double *dst = (double *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        double *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(double);
        for (j = 0; j < data->columns; ++j) {
            *cp = d_zero;
            cp += cs;
        }
        dst += data->row_strides/sizeof(double);
    }
}


#line 751
static NPY_INLINE void *
linearize_CFLOAT_matrix(void *dst_in,
                        void *src_in,
                        const LINEARIZE_DATA_t* data)
{
    COMPLEX_t *src = (COMPLEX_t *) src_in;
    COMPLEX_t *dst = (COMPLEX_t *) dst_in;

    if (dst) {
        int i, j;
        COMPLEX_t* rv = dst;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(COMPLEX_t));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(ccopy)(&columns,
                              (void*)src, &column_strides,
                              (void*)dst, &one);
            }
            else if (column_strides < 0) {
                FNAME(ccopy)(&columns,
                              (void*)((COMPLEX_t*)src + (columns-1)*column_strides),
                              &column_strides,
                              (void*)dst, &one);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                for (j = 0; j < columns; ++j) {
                    memcpy((COMPLEX_t*)dst + j, (COMPLEX_t*)src, sizeof(COMPLEX_t));
                }
            }
            src += data->row_strides/sizeof(COMPLEX_t);
            dst += data->output_lead_dim;
        }
        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void *
delinearize_CFLOAT_matrix(void *dst_in,
                          void *src_in,
                          const LINEARIZE_DATA_t* data)
{
    COMPLEX_t *src = (COMPLEX_t *) src_in;
    COMPLEX_t *dst = (COMPLEX_t *) dst_in;

    if (src) {
        int i;
        COMPLEX_t *rv = src;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(COMPLEX_t));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(ccopy)(&columns,
                              (void*)src, &one,
                              (void*)dst, &column_strides);
            }
            else if (column_strides < 0) {
                FNAME(ccopy)(&columns,
                              (void*)src, &one,
                              (void*)((COMPLEX_t*)dst + (columns-1)*column_strides),
                              &column_strides);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                if (columns > 0) {
                    memcpy((COMPLEX_t*)dst,
                           (COMPLEX_t*)src + (columns-1),
                           sizeof(COMPLEX_t));
                }
            }
            src += data->output_lead_dim;
            dst += data->row_strides/sizeof(COMPLEX_t);
        }

        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void
nan_CFLOAT_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    COMPLEX_t *dst = (COMPLEX_t *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        COMPLEX_t *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(COMPLEX_t);
        for (j = 0; j < data->columns; ++j) {
            *cp = c_nan;
            cp += cs;
        }
        dst += data->row_strides/sizeof(COMPLEX_t);
    }
}

static NPY_INLINE void
zero_CFLOAT_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    COMPLEX_t *dst = (COMPLEX_t *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        COMPLEX_t *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(COMPLEX_t);
        for (j = 0; j < data->columns; ++j) {
            *cp = c_zero;
            cp += cs;
        }
        dst += data->row_strides/sizeof(COMPLEX_t);
    }
}


#line 751
static NPY_INLINE void *
linearize_CDOUBLE_matrix(void *dst_in,
                        void *src_in,
                        const LINEARIZE_DATA_t* data)
{
    DOUBLECOMPLEX_t *src = (DOUBLECOMPLEX_t *) src_in;
    DOUBLECOMPLEX_t *dst = (DOUBLECOMPLEX_t *) dst_in;

    if (dst) {
        int i, j;
        DOUBLECOMPLEX_t* rv = dst;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(DOUBLECOMPLEX_t));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(zcopy)(&columns,
                              (void*)src, &column_strides,
                              (void*)dst, &one);
            }
            else if (column_strides < 0) {
                FNAME(zcopy)(&columns,
                              (void*)((DOUBLECOMPLEX_t*)src + (columns-1)*column_strides),
                              &column_strides,
                              (void*)dst, &one);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                for (j = 0; j < columns; ++j) {
                    memcpy((DOUBLECOMPLEX_t*)dst + j, (DOUBLECOMPLEX_t*)src, sizeof(DOUBLECOMPLEX_t));
                }
            }
            src += data->row_strides/sizeof(DOUBLECOMPLEX_t);
            dst += data->output_lead_dim;
        }
        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void *
delinearize_CDOUBLE_matrix(void *dst_in,
                          void *src_in,
                          const LINEARIZE_DATA_t* data)
{
    DOUBLECOMPLEX_t *src = (DOUBLECOMPLEX_t *) src_in;
    DOUBLECOMPLEX_t *dst = (DOUBLECOMPLEX_t *) dst_in;

    if (src) {
        int i;
        DOUBLECOMPLEX_t *rv = src;
        fortran_int columns = (fortran_int)data->columns;
        fortran_int column_strides =
            (fortran_int)(data->column_strides/sizeof(DOUBLECOMPLEX_t));
        fortran_int one = 1;
        for (i = 0; i < data->rows; i++) {
            if (column_strides > 0) {
                FNAME(zcopy)(&columns,
                              (void*)src, &one,
                              (void*)dst, &column_strides);
            }
            else if (column_strides < 0) {
                FNAME(zcopy)(&columns,
                              (void*)src, &one,
                              (void*)((DOUBLECOMPLEX_t*)dst + (columns-1)*column_strides),
                              &column_strides);
            }
            else {
                /*
                 * Zero stride has undefined behavior in some BLAS
                 * implementations (e.g. OSX Accelerate), so do it
                 * manually
                 */
                if (columns > 0) {
                    memcpy((DOUBLECOMPLEX_t*)dst,
                           (DOUBLECOMPLEX_t*)src + (columns-1),
                           sizeof(DOUBLECOMPLEX_t));
                }
            }
            src += data->output_lead_dim;
            dst += data->row_strides/sizeof(DOUBLECOMPLEX_t);
        }

        return rv;
    } else {
        return src;
    }
}

static NPY_INLINE void
nan_CDOUBLE_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    DOUBLECOMPLEX_t *dst = (DOUBLECOMPLEX_t *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        DOUBLECOMPLEX_t *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(DOUBLECOMPLEX_t);
        for (j = 0; j < data->columns; ++j) {
            *cp = z_nan;
            cp += cs;
        }
        dst += data->row_strides/sizeof(DOUBLECOMPLEX_t);
    }
}

static NPY_INLINE void
zero_CDOUBLE_matrix(void *dst_in, const LINEARIZE_DATA_t* data)
{
    DOUBLECOMPLEX_t *dst = (DOUBLECOMPLEX_t *) dst_in;

    int i, j;
    for (i = 0; i < data->rows; i++) {
        DOUBLECOMPLEX_t *cp = dst;
        ptrdiff_t cs = data->column_strides/sizeof(DOUBLECOMPLEX_t);
        for (j = 0; j < data->columns; ++j) {
            *cp = z_zero;
            cp += cs;
        }
        dst += data->row_strides/sizeof(DOUBLECOMPLEX_t);
    }
}



               /* identity square matrix generation */
#line 888
static NPY_INLINE void
identity_FLOAT_matrix(void *ptr, size_t n)
{
    size_t i;
    float *matrix = (float*) ptr;
    /* in IEEE floating point, zeroes are represented as bitwise 0 */
    memset(matrix, 0, n*n*sizeof(float));

    for (i = 0; i < n; ++i)
    {
        *matrix = s_one;
        matrix += n+1;
    }
}

#line 888
static NPY_INLINE void
identity_DOUBLE_matrix(void *ptr, size_t n)
{
    size_t i;
    double *matrix = (double*) ptr;
    /* in IEEE floating point, zeroes are represented as bitwise 0 */
    memset(matrix, 0, n*n*sizeof(double));

    for (i = 0; i < n; ++i)
    {
        *matrix = d_one;
        matrix += n+1;
    }
}

#line 888
static NPY_INLINE void
identity_CFLOAT_matrix(void *ptr, size_t n)
{
    size_t i;
    COMPLEX_t *matrix = (COMPLEX_t*) ptr;
    /* in IEEE floating point, zeroes are represented as bitwise 0 */
    memset(matrix, 0, n*n*sizeof(COMPLEX_t));

    for (i = 0; i < n; ++i)
    {
        *matrix = c_one;
        matrix += n+1;
    }
}

#line 888
static NPY_INLINE void
identity_CDOUBLE_matrix(void *ptr, size_t n)
{
    size_t i;
    DOUBLECOMPLEX_t *matrix = (DOUBLECOMPLEX_t*) ptr;
    /* in IEEE floating point, zeroes are represented as bitwise 0 */
    memset(matrix, 0, n*n*sizeof(DOUBLECOMPLEX_t));

    for (i = 0; i < n; ++i)
    {
        *matrix = z_one;
        matrix += n+1;
    }
}


         /* lower/upper triangular matrix using blas (in place) */
#line 911

static NPY_INLINE void
triu_FLOAT_matrix(void *ptr, size_t n)
{
    size_t i, j;
    float *matrix = (float*)ptr;
    matrix += n;
    for (i = 1; i < n; ++i) {
        for (j = 0; j < i; ++j) {
            matrix[j] = s_zero;
        }
        matrix += n;
    }
}

#line 911

static NPY_INLINE void
triu_DOUBLE_matrix(void *ptr, size_t n)
{
    size_t i, j;
    double *matrix = (double*)ptr;
    matrix += n;
    for (i = 1; i < n; ++i) {
        for (j = 0; j < i; ++j) {
            matrix[j] = d_zero;
        }
        matrix += n;
    }
}

#line 911

static NPY_INLINE void
triu_CFLOAT_matrix(void *ptr, size_t n)
{
    size_t i, j;
    COMPLEX_t *matrix = (COMPLEX_t*)ptr;
    matrix += n;
    for (i = 1; i < n; ++i) {
        for (j = 0; j < i; ++j) {
            matrix[j] = c_zero;
        }
        matrix += n;
    }
}

#line 911

static NPY_INLINE void
triu_CDOUBLE_matrix(void *ptr, size_t n)
{
    size_t i, j;
    DOUBLECOMPLEX_t *matrix = (DOUBLECOMPLEX_t*)ptr;
    matrix += n;
    for (i = 1; i < n; ++i) {
        for (j = 0; j < i; ++j) {
            matrix[j] = z_zero;
        }
        matrix += n;
    }
}



/* -------------------------------------------------------------------------- */
                          /* Determinants */

#line 938

static NPY_INLINE void
FLOAT_slogdet_from_factored_diagonal(npy_float* src,
                                      fortran_int m,
                                      npy_float *sign,
                                      npy_float *logdet)
{
    npy_float acc_sign = *sign;
    npy_float acc_logdet = 0.0f;
    int i;
    for (i = 0; i < m; i++) {
        npy_float abs_element = *src;
        if (abs_element < 0.0f) {
            acc_sign = -acc_sign;
            abs_element = -abs_element;
        }

        acc_logdet += npy_logf(abs_element);
        src += m+1;
    }

    *sign = acc_sign;
    *logdet = acc_logdet;
}

static NPY_INLINE npy_float
FLOAT_det_from_slogdet(npy_float sign, npy_float logdet)
{
    npy_float result = sign * npy_expf(logdet);
    return result;
}


#line 938

static NPY_INLINE void
DOUBLE_slogdet_from_factored_diagonal(npy_double* src,
                                      fortran_int m,
                                      npy_double *sign,
                                      npy_double *logdet)
{
    npy_double acc_sign = *sign;
    npy_double acc_logdet = 0.0;
    int i;
    for (i = 0; i < m; i++) {
        npy_double abs_element = *src;
        if (abs_element < 0.0) {
            acc_sign = -acc_sign;
            abs_element = -abs_element;
        }

        acc_logdet += npy_log(abs_element);
        src += m+1;
    }

    *sign = acc_sign;
    *logdet = acc_logdet;
}

static NPY_INLINE npy_double
DOUBLE_det_from_slogdet(npy_double sign, npy_double logdet)
{
    npy_double result = sign * npy_exp(logdet);
    return result;
}




#line 982
#define RE(COMPLEX) (((npy_float*)(&COMPLEX))[0])
#define IM(COMPLEX) (((npy_float*)(&COMPLEX))[1])

static NPY_INLINE npy_cfloat
CFLOAT_mult(npy_cfloat op1, npy_cfloat op2)
{
    npy_cfloat rv;

    RE(rv) = RE(op1)*RE(op2) - IM(op1)*IM(op2);
    IM(rv) = RE(op1)*IM(op2) + IM(op1)*RE(op2);

    return rv;
}


static NPY_INLINE void
CFLOAT_slogdet_from_factored_diagonal(npy_cfloat* src,
                                      fortran_int m,
                                      npy_cfloat *sign,
                                      npy_float *logdet)
{
    int i;
    npy_cfloat sign_acc = *sign;
    npy_float logdet_acc = 0.0f;

    for (i = 0; i < m; i++)
    {
        npy_float abs_element = npy_cabsf(*src);
        npy_cfloat sign_element;
        RE(sign_element) = RE(*src) / abs_element;
        IM(sign_element) = IM(*src) / abs_element;

        sign_acc = CFLOAT_mult(sign_acc, sign_element);
        logdet_acc += npy_logf(abs_element);
        src += m + 1;
    }

    *sign = sign_acc;
    *logdet = logdet_acc;
}

static NPY_INLINE npy_cfloat
CFLOAT_det_from_slogdet(npy_cfloat sign, npy_float logdet)
{
    npy_cfloat tmp;
    RE(tmp) = npy_expf(logdet);
    IM(tmp) = 0.0f;
    return CFLOAT_mult(sign, tmp);
}
#undef RE
#undef IM

#line 982
#define RE(COMPLEX) (((npy_double*)(&COMPLEX))[0])
#define IM(COMPLEX) (((npy_double*)(&COMPLEX))[1])

static NPY_INLINE npy_cdouble
CDOUBLE_mult(npy_cdouble op1, npy_cdouble op2)
{
    npy_cdouble rv;

    RE(rv) = RE(op1)*RE(op2) - IM(op1)*IM(op2);
    IM(rv) = RE(op1)*IM(op2) + IM(op1)*RE(op2);

    return rv;
}


static NPY_INLINE void
CDOUBLE_slogdet_from_factored_diagonal(npy_cdouble* src,
                                      fortran_int m,
                                      npy_cdouble *sign,
                                      npy_double *logdet)
{
    int i;
    npy_cdouble sign_acc = *sign;
    npy_double logdet_acc = 0.0;

    for (i = 0; i < m; i++)
    {
        npy_double abs_element = npy_cabs(*src);
        npy_cdouble sign_element;
        RE(sign_element) = RE(*src) / abs_element;
        IM(sign_element) = IM(*src) / abs_element;

        sign_acc = CDOUBLE_mult(sign_acc, sign_element);
        logdet_acc += npy_log(abs_element);
        src += m + 1;
    }

    *sign = sign_acc;
    *logdet = logdet_acc;
}

static NPY_INLINE npy_cdouble
CDOUBLE_det_from_slogdet(npy_cdouble sign, npy_double logdet)
{
    npy_cdouble tmp;
    RE(tmp) = npy_exp(logdet);
    IM(tmp) = 0.0;
    return CDOUBLE_mult(sign, tmp);
}
#undef RE
#undef IM



/* As in the linalg package, the determinant is computed via LU factorization
 * using LAPACK.
 * slogdet computes sign + log(determinant).
 * det computes sign * exp(slogdet).
 */
#line 1048

static NPY_INLINE void
FLOAT_slogdet_single_element(fortran_int m,
                              void* src,
                              fortran_int* pivots,
                              npy_float *sign,
                              npy_float *logdet)
{
    fortran_int info = 0;
    fortran_int lda = fortran_int_max(m, 1);
    int i;
    /* note: done in place */
    LAPACK(sgetrf)(&m, &m, (void *)src, &lda, pivots, &info);

    if (info == 0) {
        int change_sign = 0;
        /* note: fortran uses 1 based indexing */
        for (i = 0; i < m; i++)
        {
            change_sign += (pivots[i] != (i+1));
        }

        memcpy(sign,
               (change_sign % 2)?
                   &s_minus_one :
                   &s_one
               , sizeof(*sign));
        FLOAT_slogdet_from_factored_diagonal(src, m, sign, logdet);
    } else {
        /*
          if getrf fails, use 0 as sign and -inf as logdet
        */
        memcpy(sign, &s_zero, sizeof(*sign));
        memcpy(logdet, &s_ninf, sizeof(*logdet));
    }
}

static void
FLOAT_slogdet(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff = NULL;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *          made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *          code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_3
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_float);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);
        BEGIN_OUTER_LOOP_3
            linearize_FLOAT_matrix(tmp_buff, args[0], &lin_data);
            FLOAT_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff+matrix_size),
                                          (npy_float*)args[1],
                                          (npy_float*)args[2]);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

static void
FLOAT_det(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *       made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *       code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_2
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_float);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        npy_float sign;
        npy_float logdet;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);

        BEGIN_OUTER_LOOP_2
            linearize_FLOAT_matrix(tmp_buff, args[0], &lin_data);
            FLOAT_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff + matrix_size),
                                          &sign,
                                          &logdet);
            *(npy_float *)args[1] = FLOAT_det_from_slogdet(sign, logdet);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

#line 1048

static NPY_INLINE void
DOUBLE_slogdet_single_element(fortran_int m,
                              void* src,
                              fortran_int* pivots,
                              npy_double *sign,
                              npy_double *logdet)
{
    fortran_int info = 0;
    fortran_int lda = fortran_int_max(m, 1);
    int i;
    /* note: done in place */
    LAPACK(dgetrf)(&m, &m, (void *)src, &lda, pivots, &info);

    if (info == 0) {
        int change_sign = 0;
        /* note: fortran uses 1 based indexing */
        for (i = 0; i < m; i++)
        {
            change_sign += (pivots[i] != (i+1));
        }

        memcpy(sign,
               (change_sign % 2)?
                   &d_minus_one :
                   &d_one
               , sizeof(*sign));
        DOUBLE_slogdet_from_factored_diagonal(src, m, sign, logdet);
    } else {
        /*
          if getrf fails, use 0 as sign and -inf as logdet
        */
        memcpy(sign, &d_zero, sizeof(*sign));
        memcpy(logdet, &d_ninf, sizeof(*logdet));
    }
}

static void
DOUBLE_slogdet(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff = NULL;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *          made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *          code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_3
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_double);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);
        BEGIN_OUTER_LOOP_3
            linearize_DOUBLE_matrix(tmp_buff, args[0], &lin_data);
            DOUBLE_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff+matrix_size),
                                          (npy_double*)args[1],
                                          (npy_double*)args[2]);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

static void
DOUBLE_det(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *       made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *       code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_2
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_double);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        npy_double sign;
        npy_double logdet;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);

        BEGIN_OUTER_LOOP_2
            linearize_DOUBLE_matrix(tmp_buff, args[0], &lin_data);
            DOUBLE_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff + matrix_size),
                                          &sign,
                                          &logdet);
            *(npy_double *)args[1] = DOUBLE_det_from_slogdet(sign, logdet);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

#line 1048

static NPY_INLINE void
CFLOAT_slogdet_single_element(fortran_int m,
                              void* src,
                              fortran_int* pivots,
                              npy_cfloat *sign,
                              npy_float *logdet)
{
    fortran_int info = 0;
    fortran_int lda = fortran_int_max(m, 1);
    int i;
    /* note: done in place */
    LAPACK(cgetrf)(&m, &m, (void *)src, &lda, pivots, &info);

    if (info == 0) {
        int change_sign = 0;
        /* note: fortran uses 1 based indexing */
        for (i = 0; i < m; i++)
        {
            change_sign += (pivots[i] != (i+1));
        }

        memcpy(sign,
               (change_sign % 2)?
                   &c_minus_one :
                   &c_one
               , sizeof(*sign));
        CFLOAT_slogdet_from_factored_diagonal(src, m, sign, logdet);
    } else {
        /*
          if getrf fails, use 0 as sign and -inf as logdet
        */
        memcpy(sign, &c_zero, sizeof(*sign));
        memcpy(logdet, &c_ninf, sizeof(*logdet));
    }
}

static void
CFLOAT_slogdet(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff = NULL;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *          made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *          code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_3
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_cfloat);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);
        BEGIN_OUTER_LOOP_3
            linearize_CFLOAT_matrix(tmp_buff, args[0], &lin_data);
            CFLOAT_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff+matrix_size),
                                          (npy_cfloat*)args[1],
                                          (npy_float*)args[2]);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

static void
CFLOAT_det(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *       made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *       code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_2
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_cfloat);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        npy_cfloat sign;
        npy_float logdet;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);

        BEGIN_OUTER_LOOP_2
            linearize_CFLOAT_matrix(tmp_buff, args[0], &lin_data);
            CFLOAT_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff + matrix_size),
                                          &sign,
                                          &logdet);
            *(npy_cfloat *)args[1] = CFLOAT_det_from_slogdet(sign, logdet);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

#line 1048

static NPY_INLINE void
CDOUBLE_slogdet_single_element(fortran_int m,
                              void* src,
                              fortran_int* pivots,
                              npy_cdouble *sign,
                              npy_double *logdet)
{
    fortran_int info = 0;
    fortran_int lda = fortran_int_max(m, 1);
    int i;
    /* note: done in place */
    LAPACK(zgetrf)(&m, &m, (void *)src, &lda, pivots, &info);

    if (info == 0) {
        int change_sign = 0;
        /* note: fortran uses 1 based indexing */
        for (i = 0; i < m; i++)
        {
            change_sign += (pivots[i] != (i+1));
        }

        memcpy(sign,
               (change_sign % 2)?
                   &z_minus_one :
                   &z_one
               , sizeof(*sign));
        CDOUBLE_slogdet_from_factored_diagonal(src, m, sign, logdet);
    } else {
        /*
          if getrf fails, use 0 as sign and -inf as logdet
        */
        memcpy(sign, &z_zero, sizeof(*sign));
        memcpy(logdet, &z_ninf, sizeof(*logdet));
    }
}

static void
CDOUBLE_slogdet(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff = NULL;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *          made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *          code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_3
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_cdouble);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);
        BEGIN_OUTER_LOOP_3
            linearize_CDOUBLE_matrix(tmp_buff, args[0], &lin_data);
            CDOUBLE_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff+matrix_size),
                                          (npy_cdouble*)args[1],
                                          (npy_double*)args[2]);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}

static void
CDOUBLE_det(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    fortran_int m;
    npy_uint8 *tmp_buff;
    size_t matrix_size;
    size_t pivot_size;
    size_t safe_m;
    /* notes:
     *   matrix will need to be copied always, as factorization in lapack is
     *       made inplace
     *   matrix will need to be in column-major order, as expected by lapack
     *       code (fortran)
     *   always a square matrix
     *   need to allocate memory for both, matrix_buffer and pivot buffer
     */
    INIT_OUTER_LOOP_2
    m = (fortran_int) dimensions[0];
    safe_m = m;
    matrix_size = safe_m * safe_m * sizeof(npy_cdouble);
    pivot_size = safe_m * sizeof(fortran_int);
    tmp_buff = (npy_uint8 *)malloc(matrix_size + pivot_size);

    if (tmp_buff) {
        LINEARIZE_DATA_t lin_data;
        npy_cdouble sign;
        npy_double logdet;
        /* swapped steps to get matrix in FORTRAN order */
        init_linearize_data(&lin_data, m, m, steps[1], steps[0]);

        BEGIN_OUTER_LOOP_2
            linearize_CDOUBLE_matrix(tmp_buff, args[0], &lin_data);
            CDOUBLE_slogdet_single_element(m,
                                          (void*)tmp_buff,
                                          (fortran_int*)(tmp_buff + matrix_size),
                                          &sign,
                                          &logdet);
            *(npy_cdouble *)args[1] = CDOUBLE_det_from_slogdet(sign, logdet);
        END_OUTER_LOOP

        free(tmp_buff);
    }
}



/* -------------------------------------------------------------------------- */
                          /* Eigh family */

typedef struct eigh_params_struct {
    void *A;     /* matrix */
    void *W;     /* eigenvalue vector */
    void *WORK;  /* main work buffer */
    void *RWORK; /* secondary work buffer (for complex versions) */
    void *IWORK;
    fortran_int N;
    fortran_int LWORK;
    fortran_int LRWORK;
    fortran_int LIWORK;
    char JOBZ;
    char UPLO;
    fortran_int LDA;
} EIGH_PARAMS_t;

#line 1201

static NPY_INLINE fortran_int
call_ssyevd(EIGH_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(ssyevd)(&params->JOBZ, &params->UPLO, &params->N,
                          params->A, &params->LDA, params->W,
                          params->WORK, &params->LWORK,
                          params->IWORK, &params->LIWORK,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _syevd
 * Handles buffer allocation
 */
static NPY_INLINE int
init_ssyevd(EIGH_PARAMS_t* params, char JOBZ, char UPLO,
                   fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    fortran_int lwork;
    fortran_int liwork;
    npy_uint8 *a, *w, *work, *iwork;
    size_t safe_N = N;
    size_t alloc_size = safe_N * (safe_N + 1) * sizeof(npy_float);
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(alloc_size);

    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    w = mem_buff + safe_N * safe_N * sizeof(npy_float);

    params->A = a;
    params->W = w;
    params->RWORK = NULL; /* unused */
    params->N = N;
    params->LRWORK = 0; /* unused */
    params->JOBZ = JOBZ;
    params->UPLO = UPLO;
    params->LDA = lda;

    /* Work size query */
    {
        npy_float query_work_size;
        fortran_int query_iwork_size;

        params->LWORK = -1;
        params->LIWORK = -1;
        params->WORK = &query_work_size;
        params->IWORK = &query_iwork_size;

        if (call_ssyevd(params) != 0) {
            goto error;
        }

        lwork = (fortran_int)query_work_size;
        liwork = query_iwork_size;
    }

    mem_buff2 = malloc(lwork*sizeof(npy_float) + liwork*sizeof(fortran_int));
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;
    iwork = mem_buff2 + lwork*sizeof(npy_float);

    params->LWORK = lwork;
    params->WORK = work;
    params->LIWORK = liwork;
    params->IWORK = iwork;

    return 1;

 error:
    /* something failed */
    memset(params, 0, sizeof(*params));
    free(mem_buff2);
    free(mem_buff);

    return 0;
}

#line 1201

static NPY_INLINE fortran_int
call_dsyevd(EIGH_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(dsyevd)(&params->JOBZ, &params->UPLO, &params->N,
                          params->A, &params->LDA, params->W,
                          params->WORK, &params->LWORK,
                          params->IWORK, &params->LIWORK,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _syevd
 * Handles buffer allocation
 */
static NPY_INLINE int
init_dsyevd(EIGH_PARAMS_t* params, char JOBZ, char UPLO,
                   fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    fortran_int lwork;
    fortran_int liwork;
    npy_uint8 *a, *w, *work, *iwork;
    size_t safe_N = N;
    size_t alloc_size = safe_N * (safe_N + 1) * sizeof(npy_double);
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(alloc_size);

    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    w = mem_buff + safe_N * safe_N * sizeof(npy_double);

    params->A = a;
    params->W = w;
    params->RWORK = NULL; /* unused */
    params->N = N;
    params->LRWORK = 0; /* unused */
    params->JOBZ = JOBZ;
    params->UPLO = UPLO;
    params->LDA = lda;

    /* Work size query */
    {
        npy_double query_work_size;
        fortran_int query_iwork_size;

        params->LWORK = -1;
        params->LIWORK = -1;
        params->WORK = &query_work_size;
        params->IWORK = &query_iwork_size;

        if (call_dsyevd(params) != 0) {
            goto error;
        }

        lwork = (fortran_int)query_work_size;
        liwork = query_iwork_size;
    }

    mem_buff2 = malloc(lwork*sizeof(npy_double) + liwork*sizeof(fortran_int));
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;
    iwork = mem_buff2 + lwork*sizeof(npy_double);

    params->LWORK = lwork;
    params->WORK = work;
    params->LIWORK = liwork;
    params->IWORK = iwork;

    return 1;

 error:
    /* something failed */
    memset(params, 0, sizeof(*params));
    free(mem_buff2);
    free(mem_buff);

    return 0;
}



#line 1300
static NPY_INLINE fortran_int
call_cheevd(EIGH_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(cheevd)(&params->JOBZ, &params->UPLO, &params->N,
                          params->A, &params->LDA, params->W,
                          params->WORK, &params->LWORK,
                          params->RWORK, &params->LRWORK,
                          params->IWORK, &params->LIWORK,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _heev
 * Handles buffer allocation
 */
static NPY_INLINE int
init_cheevd(EIGH_PARAMS_t *params,
                   char JOBZ,
                   char UPLO,
                   fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    fortran_int lwork;
    fortran_int lrwork;
    fortran_int liwork;
    npy_uint8 *a, *w, *work, *rwork, *iwork;
    size_t safe_N = N;
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(safe_N * safe_N * sizeof(npy_cfloat) +
                      safe_N * sizeof(npy_float));
    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    w = mem_buff + safe_N * safe_N * sizeof(npy_cfloat);

    params->A = a;
    params->W = w;
    params->N = N;
    params->JOBZ = JOBZ;
    params->UPLO = UPLO;
    params->LDA = lda;

    /* Work size query */
    {
        fortran_complex query_work_size;
        fortran_real query_rwork_size;
        fortran_int query_iwork_size;

        params->LWORK = -1;
        params->LRWORK = -1;
        params->LIWORK = -1;
        params->WORK = &query_work_size;
        params->RWORK = &query_rwork_size;
        params->IWORK = &query_iwork_size;

        if (call_cheevd(params) != 0) {
            goto error;
        }

        lwork = (fortran_int)*(fortran_real*)&query_work_size;
        lrwork = (fortran_int)query_rwork_size;
        liwork = query_iwork_size;
    }

    mem_buff2 = malloc(lwork*sizeof(npy_cfloat) +
                       lrwork*sizeof(npy_float) +
                       liwork*sizeof(fortran_int));
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;
    rwork = work + lwork*sizeof(npy_cfloat);
    iwork = rwork + lrwork*sizeof(npy_float);

    params->WORK = work;
    params->RWORK = rwork;
    params->IWORK = iwork;
    params->LWORK = lwork;
    params->LRWORK = lrwork;
    params->LIWORK = liwork;

    return 1;

    /* something failed */
error:
    memset(params, 0, sizeof(*params));
    free(mem_buff2);
    free(mem_buff);

    return 0;
}

#line 1300
static NPY_INLINE fortran_int
call_zheevd(EIGH_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(zheevd)(&params->JOBZ, &params->UPLO, &params->N,
                          params->A, &params->LDA, params->W,
                          params->WORK, &params->LWORK,
                          params->RWORK, &params->LRWORK,
                          params->IWORK, &params->LIWORK,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _heev
 * Handles buffer allocation
 */
static NPY_INLINE int
init_zheevd(EIGH_PARAMS_t *params,
                   char JOBZ,
                   char UPLO,
                   fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    fortran_int lwork;
    fortran_int lrwork;
    fortran_int liwork;
    npy_uint8 *a, *w, *work, *rwork, *iwork;
    size_t safe_N = N;
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(safe_N * safe_N * sizeof(npy_cdouble) +
                      safe_N * sizeof(npy_double));
    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    w = mem_buff + safe_N * safe_N * sizeof(npy_cdouble);

    params->A = a;
    params->W = w;
    params->N = N;
    params->JOBZ = JOBZ;
    params->UPLO = UPLO;
    params->LDA = lda;

    /* Work size query */
    {
        fortran_doublecomplex query_work_size;
        fortran_doublereal query_rwork_size;
        fortran_int query_iwork_size;

        params->LWORK = -1;
        params->LRWORK = -1;
        params->LIWORK = -1;
        params->WORK = &query_work_size;
        params->RWORK = &query_rwork_size;
        params->IWORK = &query_iwork_size;

        if (call_zheevd(params) != 0) {
            goto error;
        }

        lwork = (fortran_int)*(fortran_doublereal*)&query_work_size;
        lrwork = (fortran_int)query_rwork_size;
        liwork = query_iwork_size;
    }

    mem_buff2 = malloc(lwork*sizeof(npy_cdouble) +
                       lrwork*sizeof(npy_double) +
                       liwork*sizeof(fortran_int));
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;
    rwork = work + lwork*sizeof(npy_cdouble);
    iwork = rwork + lrwork*sizeof(npy_double);

    params->WORK = work;
    params->RWORK = rwork;
    params->IWORK = iwork;
    params->LWORK = lwork;
    params->LRWORK = lrwork;
    params->LIWORK = liwork;

    return 1;

    /* something failed */
error:
    memset(params, 0, sizeof(*params));
    free(mem_buff2);
    free(mem_buff);

    return 0;
}



#line 1407
/*
 * (M, M)->(M,)(M, M)
 * dimensions[1] -> M
 * args[0] -> A[in]
 * args[1] -> W
 * args[2] -> A[out]
 */

static NPY_INLINE void
release_ssyevd(EIGH_PARAMS_t *params)
{
    /* allocated memory in A and WORK */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}


static NPY_INLINE void
FLOAT_eigh_wrapper(char JOBZ,
                    char UPLO,
                    char**args,
                    npy_intp const *dimensions,
                    npy_intp const *steps)
{
    ptrdiff_t outer_steps[3];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:3;
    EIGH_PARAMS_t eigh_params;
    int error_occurred = get_fp_invalid_and_clear();

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_ssyevd(&eigh_params,
                           JOBZ,
                           UPLO,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t matrix_in_ld;
        LINEARIZE_DATA_t eigenvectors_out_ld;
        LINEARIZE_DATA_t eigenvalues_out_ld;

        init_linearize_data(&matrix_in_ld,
                            eigh_params.N, eigh_params.N,
                            steps[1], steps[0]);
        init_linearize_data(&eigenvalues_out_ld,
                            1, eigh_params.N,
                            0, steps[2]);
        if ('V' == eigh_params.JOBZ) {
            init_linearize_data(&eigenvectors_out_ld,
                                eigh_params.N, eigh_params.N,
                                steps[4], steps[3]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_FLOAT_matrix(eigh_params.A, args[0], &matrix_in_ld);
            not_ok = call_ssyevd(&eigh_params);
            if (!not_ok) {
                /* lapack ok, copy result out */
                delinearize_FLOAT_matrix(args[1],
                                              eigh_params.W,
                                              &eigenvalues_out_ld);

                if ('V' == eigh_params.JOBZ) {
                    delinearize_FLOAT_matrix(args[2],
                                              eigh_params.A,
                                              &eigenvectors_out_ld);
                }
            } else {
                /* lapack fail, set result to nan */
                error_occurred = 1;
                nan_FLOAT_matrix(args[1], &eigenvalues_out_ld);
                if ('V' == eigh_params.JOBZ) {
                    nan_FLOAT_matrix(args[2], &eigenvectors_out_ld);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_ssyevd(&eigh_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

#line 1407
/*
 * (M, M)->(M,)(M, M)
 * dimensions[1] -> M
 * args[0] -> A[in]
 * args[1] -> W
 * args[2] -> A[out]
 */

static NPY_INLINE void
release_dsyevd(EIGH_PARAMS_t *params)
{
    /* allocated memory in A and WORK */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}


static NPY_INLINE void
DOUBLE_eigh_wrapper(char JOBZ,
                    char UPLO,
                    char**args,
                    npy_intp const *dimensions,
                    npy_intp const *steps)
{
    ptrdiff_t outer_steps[3];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:3;
    EIGH_PARAMS_t eigh_params;
    int error_occurred = get_fp_invalid_and_clear();

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_dsyevd(&eigh_params,
                           JOBZ,
                           UPLO,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t matrix_in_ld;
        LINEARIZE_DATA_t eigenvectors_out_ld;
        LINEARIZE_DATA_t eigenvalues_out_ld;

        init_linearize_data(&matrix_in_ld,
                            eigh_params.N, eigh_params.N,
                            steps[1], steps[0]);
        init_linearize_data(&eigenvalues_out_ld,
                            1, eigh_params.N,
                            0, steps[2]);
        if ('V' == eigh_params.JOBZ) {
            init_linearize_data(&eigenvectors_out_ld,
                                eigh_params.N, eigh_params.N,
                                steps[4], steps[3]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_DOUBLE_matrix(eigh_params.A, args[0], &matrix_in_ld);
            not_ok = call_dsyevd(&eigh_params);
            if (!not_ok) {
                /* lapack ok, copy result out */
                delinearize_DOUBLE_matrix(args[1],
                                              eigh_params.W,
                                              &eigenvalues_out_ld);

                if ('V' == eigh_params.JOBZ) {
                    delinearize_DOUBLE_matrix(args[2],
                                              eigh_params.A,
                                              &eigenvectors_out_ld);
                }
            } else {
                /* lapack fail, set result to nan */
                error_occurred = 1;
                nan_DOUBLE_matrix(args[1], &eigenvalues_out_ld);
                if ('V' == eigh_params.JOBZ) {
                    nan_DOUBLE_matrix(args[2], &eigenvectors_out_ld);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_dsyevd(&eigh_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

#line 1407
/*
 * (M, M)->(M,)(M, M)
 * dimensions[1] -> M
 * args[0] -> A[in]
 * args[1] -> W
 * args[2] -> A[out]
 */

static NPY_INLINE void
release_cheevd(EIGH_PARAMS_t *params)
{
    /* allocated memory in A and WORK */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}


static NPY_INLINE void
CFLOAT_eigh_wrapper(char JOBZ,
                    char UPLO,
                    char**args,
                    npy_intp const *dimensions,
                    npy_intp const *steps)
{
    ptrdiff_t outer_steps[3];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:3;
    EIGH_PARAMS_t eigh_params;
    int error_occurred = get_fp_invalid_and_clear();

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_cheevd(&eigh_params,
                           JOBZ,
                           UPLO,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t matrix_in_ld;
        LINEARIZE_DATA_t eigenvectors_out_ld;
        LINEARIZE_DATA_t eigenvalues_out_ld;

        init_linearize_data(&matrix_in_ld,
                            eigh_params.N, eigh_params.N,
                            steps[1], steps[0]);
        init_linearize_data(&eigenvalues_out_ld,
                            1, eigh_params.N,
                            0, steps[2]);
        if ('V' == eigh_params.JOBZ) {
            init_linearize_data(&eigenvectors_out_ld,
                                eigh_params.N, eigh_params.N,
                                steps[4], steps[3]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_CFLOAT_matrix(eigh_params.A, args[0], &matrix_in_ld);
            not_ok = call_cheevd(&eigh_params);
            if (!not_ok) {
                /* lapack ok, copy result out */
                delinearize_FLOAT_matrix(args[1],
                                              eigh_params.W,
                                              &eigenvalues_out_ld);

                if ('V' == eigh_params.JOBZ) {
                    delinearize_CFLOAT_matrix(args[2],
                                              eigh_params.A,
                                              &eigenvectors_out_ld);
                }
            } else {
                /* lapack fail, set result to nan */
                error_occurred = 1;
                nan_FLOAT_matrix(args[1], &eigenvalues_out_ld);
                if ('V' == eigh_params.JOBZ) {
                    nan_CFLOAT_matrix(args[2], &eigenvectors_out_ld);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_cheevd(&eigh_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

#line 1407
/*
 * (M, M)->(M,)(M, M)
 * dimensions[1] -> M
 * args[0] -> A[in]
 * args[1] -> W
 * args[2] -> A[out]
 */

static NPY_INLINE void
release_zheevd(EIGH_PARAMS_t *params)
{
    /* allocated memory in A and WORK */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}


static NPY_INLINE void
CDOUBLE_eigh_wrapper(char JOBZ,
                    char UPLO,
                    char**args,
                    npy_intp const *dimensions,
                    npy_intp const *steps)
{
    ptrdiff_t outer_steps[3];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:3;
    EIGH_PARAMS_t eigh_params;
    int error_occurred = get_fp_invalid_and_clear();

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_zheevd(&eigh_params,
                           JOBZ,
                           UPLO,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t matrix_in_ld;
        LINEARIZE_DATA_t eigenvectors_out_ld;
        LINEARIZE_DATA_t eigenvalues_out_ld;

        init_linearize_data(&matrix_in_ld,
                            eigh_params.N, eigh_params.N,
                            steps[1], steps[0]);
        init_linearize_data(&eigenvalues_out_ld,
                            1, eigh_params.N,
                            0, steps[2]);
        if ('V' == eigh_params.JOBZ) {
            init_linearize_data(&eigenvectors_out_ld,
                                eigh_params.N, eigh_params.N,
                                steps[4], steps[3]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_CDOUBLE_matrix(eigh_params.A, args[0], &matrix_in_ld);
            not_ok = call_zheevd(&eigh_params);
            if (!not_ok) {
                /* lapack ok, copy result out */
                delinearize_DOUBLE_matrix(args[1],
                                              eigh_params.W,
                                              &eigenvalues_out_ld);

                if ('V' == eigh_params.JOBZ) {
                    delinearize_CDOUBLE_matrix(args[2],
                                              eigh_params.A,
                                              &eigenvectors_out_ld);
                }
            } else {
                /* lapack fail, set result to nan */
                error_occurred = 1;
                nan_DOUBLE_matrix(args[1], &eigenvalues_out_ld);
                if ('V' == eigh_params.JOBZ) {
                    nan_CDOUBLE_matrix(args[2], &eigenvectors_out_ld);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_zheevd(&eigh_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}



#line 1502
static void
FLOAT_eighlo(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    FLOAT_eigh_wrapper('V', 'L', args, dimensions, steps);
}

static void
FLOAT_eighup(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void* NPY_UNUSED(func))
{
    FLOAT_eigh_wrapper('V', 'U', args, dimensions, steps);
}

static void
FLOAT_eigvalshlo(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    FLOAT_eigh_wrapper('N', 'L', args, dimensions, steps);
}

static void
FLOAT_eigvalshup(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    FLOAT_eigh_wrapper('N', 'U', args, dimensions, steps);
}

#line 1502
static void
DOUBLE_eighlo(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    DOUBLE_eigh_wrapper('V', 'L', args, dimensions, steps);
}

static void
DOUBLE_eighup(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void* NPY_UNUSED(func))
{
    DOUBLE_eigh_wrapper('V', 'U', args, dimensions, steps);
}

static void
DOUBLE_eigvalshlo(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    DOUBLE_eigh_wrapper('N', 'L', args, dimensions, steps);
}

static void
DOUBLE_eigvalshup(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    DOUBLE_eigh_wrapper('N', 'U', args, dimensions, steps);
}

#line 1502
static void
CFLOAT_eighlo(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    CFLOAT_eigh_wrapper('V', 'L', args, dimensions, steps);
}

static void
CFLOAT_eighup(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void* NPY_UNUSED(func))
{
    CFLOAT_eigh_wrapper('V', 'U', args, dimensions, steps);
}

static void
CFLOAT_eigvalshlo(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    CFLOAT_eigh_wrapper('N', 'L', args, dimensions, steps);
}

static void
CFLOAT_eigvalshup(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    CFLOAT_eigh_wrapper('N', 'U', args, dimensions, steps);
}

#line 1502
static void
CDOUBLE_eighlo(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    CDOUBLE_eigh_wrapper('V', 'L', args, dimensions, steps);
}

static void
CDOUBLE_eighup(char **args,
              npy_intp const *dimensions,
              npy_intp const *steps,
              void* NPY_UNUSED(func))
{
    CDOUBLE_eigh_wrapper('V', 'U', args, dimensions, steps);
}

static void
CDOUBLE_eigvalshlo(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    CDOUBLE_eigh_wrapper('N', 'L', args, dimensions, steps);
}

static void
CDOUBLE_eigvalshup(char **args,
                  npy_intp const *dimensions,
                  npy_intp const *steps,
                  void* NPY_UNUSED(func))
{
    CDOUBLE_eigh_wrapper('N', 'U', args, dimensions, steps);
}


/* -------------------------------------------------------------------------- */
                  /* Solve family (includes inv) */

typedef struct gesv_params_struct
{
    void *A; /* A is (N, N) of base type */
    void *B; /* B is (N, NRHS) of base type */
    fortran_int * IPIV; /* IPIV is (N) */

    fortran_int N;
    fortran_int NRHS;
    fortran_int LDA;
    fortran_int LDB;
} GESV_PARAMS_t;

#line 1561

static NPY_INLINE fortran_int
call_sgesv(GESV_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(sgesv)(&params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->IPIV,
                          params->B, &params->LDB,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _heev
 * Handles buffer allocation
 */
static NPY_INLINE int
init_sgesv(GESV_PARAMS_t *params, fortran_int N, fortran_int NRHS)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a, *b, *ipiv;
    size_t safe_N = N;
    size_t safe_NRHS = NRHS;
    fortran_int ld = fortran_int_max(N, 1);
    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_real) +
                      safe_N * safe_NRHS*sizeof(fortran_real) +
                      safe_N * sizeof(fortran_int));
    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    b = a + safe_N * safe_N * sizeof(fortran_real);
    ipiv = b + safe_N * safe_NRHS * sizeof(fortran_real);

    params->A = a;
    params->B = b;
    params->IPIV = (fortran_int*)ipiv;
    params->N = N;
    params->NRHS = NRHS;
    params->LDA = ld;
    params->LDB = ld;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_sgesv(GESV_PARAMS_t *params)
{
    /* memory block base is in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
FLOAT_solve(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n, nrhs;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    nrhs = (fortran_int)dimensions[1];
    if (init_sgesv(&params, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;

        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, nrhs, n, steps[3], steps[2]);
        init_linearize_data(&r_out, nrhs, n, steps[5], steps[4]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_FLOAT_matrix(params.A, args[0], &a_in);
            linearize_FLOAT_matrix(params.B, args[1], &b_in);
            not_ok =call_sgesv(&params);
            if (!not_ok) {
                delinearize_FLOAT_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_FLOAT_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_sgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
FLOAT_solve1(char **args, npy_intp const *dimensions, npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    if (init_sgesv(&params, n, 1)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, 1, n, 1, steps[2]);
        init_linearize_data(&r_out, 1, n, 1, steps[3]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_FLOAT_matrix(params.A, args[0], &a_in);
            linearize_FLOAT_matrix(params.B, args[1], &b_in);
            not_ok = call_sgesv(&params);
            if (!not_ok) {
                delinearize_FLOAT_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_FLOAT_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_sgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
FLOAT_inv(char **args, npy_intp const *dimensions, npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_2

    n = (fortran_int)dimensions[0];
    if (init_sgesv(&params, n, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);

        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_FLOAT_matrix(params.A, args[0], &a_in);
            identity_FLOAT_matrix(params.B, n);
            not_ok = call_sgesv(&params);
            if (!not_ok) {
                delinearize_FLOAT_matrix(args[1], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_FLOAT_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP

        release_sgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}


#line 1561

static NPY_INLINE fortran_int
call_dgesv(GESV_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(dgesv)(&params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->IPIV,
                          params->B, &params->LDB,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _heev
 * Handles buffer allocation
 */
static NPY_INLINE int
init_dgesv(GESV_PARAMS_t *params, fortran_int N, fortran_int NRHS)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a, *b, *ipiv;
    size_t safe_N = N;
    size_t safe_NRHS = NRHS;
    fortran_int ld = fortran_int_max(N, 1);
    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_doublereal) +
                      safe_N * safe_NRHS*sizeof(fortran_doublereal) +
                      safe_N * sizeof(fortran_int));
    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    b = a + safe_N * safe_N * sizeof(fortran_doublereal);
    ipiv = b + safe_N * safe_NRHS * sizeof(fortran_doublereal);

    params->A = a;
    params->B = b;
    params->IPIV = (fortran_int*)ipiv;
    params->N = N;
    params->NRHS = NRHS;
    params->LDA = ld;
    params->LDB = ld;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_dgesv(GESV_PARAMS_t *params)
{
    /* memory block base is in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
DOUBLE_solve(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n, nrhs;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    nrhs = (fortran_int)dimensions[1];
    if (init_dgesv(&params, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;

        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, nrhs, n, steps[3], steps[2]);
        init_linearize_data(&r_out, nrhs, n, steps[5], steps[4]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_DOUBLE_matrix(params.A, args[0], &a_in);
            linearize_DOUBLE_matrix(params.B, args[1], &b_in);
            not_ok =call_dgesv(&params);
            if (!not_ok) {
                delinearize_DOUBLE_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_DOUBLE_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_dgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
DOUBLE_solve1(char **args, npy_intp const *dimensions, npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    if (init_dgesv(&params, n, 1)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, 1, n, 1, steps[2]);
        init_linearize_data(&r_out, 1, n, 1, steps[3]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_DOUBLE_matrix(params.A, args[0], &a_in);
            linearize_DOUBLE_matrix(params.B, args[1], &b_in);
            not_ok = call_dgesv(&params);
            if (!not_ok) {
                delinearize_DOUBLE_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_DOUBLE_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_dgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
DOUBLE_inv(char **args, npy_intp const *dimensions, npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_2

    n = (fortran_int)dimensions[0];
    if (init_dgesv(&params, n, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);

        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_DOUBLE_matrix(params.A, args[0], &a_in);
            identity_DOUBLE_matrix(params.B, n);
            not_ok = call_dgesv(&params);
            if (!not_ok) {
                delinearize_DOUBLE_matrix(args[1], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_DOUBLE_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP

        release_dgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}


#line 1561

static NPY_INLINE fortran_int
call_cgesv(GESV_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(cgesv)(&params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->IPIV,
                          params->B, &params->LDB,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _heev
 * Handles buffer allocation
 */
static NPY_INLINE int
init_cgesv(GESV_PARAMS_t *params, fortran_int N, fortran_int NRHS)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a, *b, *ipiv;
    size_t safe_N = N;
    size_t safe_NRHS = NRHS;
    fortran_int ld = fortran_int_max(N, 1);
    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_complex) +
                      safe_N * safe_NRHS*sizeof(fortran_complex) +
                      safe_N * sizeof(fortran_int));
    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    b = a + safe_N * safe_N * sizeof(fortran_complex);
    ipiv = b + safe_N * safe_NRHS * sizeof(fortran_complex);

    params->A = a;
    params->B = b;
    params->IPIV = (fortran_int*)ipiv;
    params->N = N;
    params->NRHS = NRHS;
    params->LDA = ld;
    params->LDB = ld;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_cgesv(GESV_PARAMS_t *params)
{
    /* memory block base is in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
CFLOAT_solve(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n, nrhs;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    nrhs = (fortran_int)dimensions[1];
    if (init_cgesv(&params, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;

        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, nrhs, n, steps[3], steps[2]);
        init_linearize_data(&r_out, nrhs, n, steps[5], steps[4]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_CFLOAT_matrix(params.A, args[0], &a_in);
            linearize_CFLOAT_matrix(params.B, args[1], &b_in);
            not_ok =call_cgesv(&params);
            if (!not_ok) {
                delinearize_CFLOAT_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_CFLOAT_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_cgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CFLOAT_solve1(char **args, npy_intp const *dimensions, npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    if (init_cgesv(&params, n, 1)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, 1, n, 1, steps[2]);
        init_linearize_data(&r_out, 1, n, 1, steps[3]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_CFLOAT_matrix(params.A, args[0], &a_in);
            linearize_CFLOAT_matrix(params.B, args[1], &b_in);
            not_ok = call_cgesv(&params);
            if (!not_ok) {
                delinearize_CFLOAT_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_CFLOAT_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_cgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CFLOAT_inv(char **args, npy_intp const *dimensions, npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_2

    n = (fortran_int)dimensions[0];
    if (init_cgesv(&params, n, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);

        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_CFLOAT_matrix(params.A, args[0], &a_in);
            identity_CFLOAT_matrix(params.B, n);
            not_ok = call_cgesv(&params);
            if (!not_ok) {
                delinearize_CFLOAT_matrix(args[1], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_CFLOAT_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP

        release_cgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}


#line 1561

static NPY_INLINE fortran_int
call_zgesv(GESV_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(zgesv)(&params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->IPIV,
                          params->B, &params->LDB,
                          &rv);
    return rv;
}

/*
 * Initialize the parameters to use in for the lapack function _heev
 * Handles buffer allocation
 */
static NPY_INLINE int
init_zgesv(GESV_PARAMS_t *params, fortran_int N, fortran_int NRHS)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a, *b, *ipiv;
    size_t safe_N = N;
    size_t safe_NRHS = NRHS;
    fortran_int ld = fortran_int_max(N, 1);
    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_doublecomplex) +
                      safe_N * safe_NRHS*sizeof(fortran_doublecomplex) +
                      safe_N * sizeof(fortran_int));
    if (!mem_buff) {
        goto error;
    }
    a = mem_buff;
    b = a + safe_N * safe_N * sizeof(fortran_doublecomplex);
    ipiv = b + safe_N * safe_NRHS * sizeof(fortran_doublecomplex);

    params->A = a;
    params->B = b;
    params->IPIV = (fortran_int*)ipiv;
    params->N = N;
    params->NRHS = NRHS;
    params->LDA = ld;
    params->LDB = ld;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_zgesv(GESV_PARAMS_t *params)
{
    /* memory block base is in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
CDOUBLE_solve(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n, nrhs;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    nrhs = (fortran_int)dimensions[1];
    if (init_zgesv(&params, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;

        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, nrhs, n, steps[3], steps[2]);
        init_linearize_data(&r_out, nrhs, n, steps[5], steps[4]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_CDOUBLE_matrix(params.A, args[0], &a_in);
            linearize_CDOUBLE_matrix(params.B, args[1], &b_in);
            not_ok =call_zgesv(&params);
            if (!not_ok) {
                delinearize_CDOUBLE_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_CDOUBLE_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_zgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CDOUBLE_solve1(char **args, npy_intp const *dimensions, npy_intp const *steps,
              void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_3

    n = (fortran_int)dimensions[0];
    if (init_zgesv(&params, n, 1)) {
        LINEARIZE_DATA_t a_in, b_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&b_in, 1, n, 1, steps[2]);
        init_linearize_data(&r_out, 1, n, 1, steps[3]);

        BEGIN_OUTER_LOOP_3
            int not_ok;
            linearize_CDOUBLE_matrix(params.A, args[0], &a_in);
            linearize_CDOUBLE_matrix(params.B, args[1], &b_in);
            not_ok = call_zgesv(&params);
            if (!not_ok) {
                delinearize_CDOUBLE_matrix(args[2], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_CDOUBLE_matrix(args[2], &r_out);
            }
        END_OUTER_LOOP

        release_zgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CDOUBLE_inv(char **args, npy_intp const *dimensions, npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    GESV_PARAMS_t params;
    fortran_int n;
    int error_occurred = get_fp_invalid_and_clear();
    INIT_OUTER_LOOP_2

    n = (fortran_int)dimensions[0];
    if (init_zgesv(&params, n, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);

        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_CDOUBLE_matrix(params.A, args[0], &a_in);
            identity_CDOUBLE_matrix(params.B, n);
            not_ok = call_zgesv(&params);
            if (!not_ok) {
                delinearize_CDOUBLE_matrix(args[1], params.B, &r_out);
            } else {
                error_occurred = 1;
                nan_CDOUBLE_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP

        release_zgesv(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}




/* -------------------------------------------------------------------------- */
                     /* Cholesky decomposition */

typedef struct potr_params_struct
{
    void *A;
    fortran_int N;
    fortran_int LDA;
    char UPLO;
} POTR_PARAMS_t;

#line 1747

static NPY_INLINE fortran_int
call_spotrf(POTR_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(spotrf)(&params->UPLO,
                          &params->N, params->A, &params->LDA,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_spotrf(POTR_PARAMS_t *params, char UPLO, fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a;
    size_t safe_N = N;
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_real));
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;

    params->A = a;
    params->N = N;
    params->LDA = lda;
    params->UPLO = UPLO;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_spotrf(POTR_PARAMS_t *params)
{
    /* memory block base in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
FLOAT_cholesky(char uplo, char **args, npy_intp const *dimensions, npy_intp const *steps)
{
    POTR_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_2

    assert(uplo == 'L');

    n = (fortran_int)dimensions[0];
    if (init_spotrf(&params, uplo, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);
        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_FLOAT_matrix(params.A, args[0], &a_in);
            not_ok = call_spotrf(&params);
            if (!not_ok) {
                triu_FLOAT_matrix(params.A, params.N);
                delinearize_FLOAT_matrix(args[1], params.A, &r_out);
            } else {
                error_occurred = 1;
                nan_FLOAT_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP
        release_spotrf(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
FLOAT_cholesky_lo(char **args, npy_intp const *dimensions, npy_intp const *steps,
                void *NPY_UNUSED(func))
{
    FLOAT_cholesky('L', args, dimensions, steps);
}


#line 1747

static NPY_INLINE fortran_int
call_dpotrf(POTR_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(dpotrf)(&params->UPLO,
                          &params->N, params->A, &params->LDA,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_dpotrf(POTR_PARAMS_t *params, char UPLO, fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a;
    size_t safe_N = N;
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_doublereal));
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;

    params->A = a;
    params->N = N;
    params->LDA = lda;
    params->UPLO = UPLO;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_dpotrf(POTR_PARAMS_t *params)
{
    /* memory block base in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
DOUBLE_cholesky(char uplo, char **args, npy_intp const *dimensions, npy_intp const *steps)
{
    POTR_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_2

    assert(uplo == 'L');

    n = (fortran_int)dimensions[0];
    if (init_dpotrf(&params, uplo, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);
        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_DOUBLE_matrix(params.A, args[0], &a_in);
            not_ok = call_dpotrf(&params);
            if (!not_ok) {
                triu_DOUBLE_matrix(params.A, params.N);
                delinearize_DOUBLE_matrix(args[1], params.A, &r_out);
            } else {
                error_occurred = 1;
                nan_DOUBLE_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP
        release_dpotrf(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
DOUBLE_cholesky_lo(char **args, npy_intp const *dimensions, npy_intp const *steps,
                void *NPY_UNUSED(func))
{
    DOUBLE_cholesky('L', args, dimensions, steps);
}


#line 1747

static NPY_INLINE fortran_int
call_cpotrf(POTR_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(cpotrf)(&params->UPLO,
                          &params->N, params->A, &params->LDA,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_cpotrf(POTR_PARAMS_t *params, char UPLO, fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a;
    size_t safe_N = N;
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_complex));
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;

    params->A = a;
    params->N = N;
    params->LDA = lda;
    params->UPLO = UPLO;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_cpotrf(POTR_PARAMS_t *params)
{
    /* memory block base in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
CFLOAT_cholesky(char uplo, char **args, npy_intp const *dimensions, npy_intp const *steps)
{
    POTR_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_2

    assert(uplo == 'L');

    n = (fortran_int)dimensions[0];
    if (init_cpotrf(&params, uplo, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);
        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_CFLOAT_matrix(params.A, args[0], &a_in);
            not_ok = call_cpotrf(&params);
            if (!not_ok) {
                triu_CFLOAT_matrix(params.A, params.N);
                delinearize_CFLOAT_matrix(args[1], params.A, &r_out);
            } else {
                error_occurred = 1;
                nan_CFLOAT_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP
        release_cpotrf(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CFLOAT_cholesky_lo(char **args, npy_intp const *dimensions, npy_intp const *steps,
                void *NPY_UNUSED(func))
{
    CFLOAT_cholesky('L', args, dimensions, steps);
}


#line 1747

static NPY_INLINE fortran_int
call_zpotrf(POTR_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(zpotrf)(&params->UPLO,
                          &params->N, params->A, &params->LDA,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_zpotrf(POTR_PARAMS_t *params, char UPLO, fortran_int N)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *a;
    size_t safe_N = N;
    fortran_int lda = fortran_int_max(N, 1);

    mem_buff = malloc(safe_N * safe_N * sizeof(fortran_doublecomplex));
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;

    params->A = a;
    params->N = N;
    params->LDA = lda;
    params->UPLO = UPLO;

    return 1;
 error:
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
release_zpotrf(POTR_PARAMS_t *params)
{
    /* memory block base in A */
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static void
CDOUBLE_cholesky(char uplo, char **args, npy_intp const *dimensions, npy_intp const *steps)
{
    POTR_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n;
    INIT_OUTER_LOOP_2

    assert(uplo == 'L');

    n = (fortran_int)dimensions[0];
    if (init_zpotrf(&params, uplo, n)) {
        LINEARIZE_DATA_t a_in, r_out;
        init_linearize_data(&a_in, n, n, steps[1], steps[0]);
        init_linearize_data(&r_out, n, n, steps[3], steps[2]);
        BEGIN_OUTER_LOOP_2
            int not_ok;
            linearize_CDOUBLE_matrix(params.A, args[0], &a_in);
            not_ok = call_zpotrf(&params);
            if (!not_ok) {
                triu_CDOUBLE_matrix(params.A, params.N);
                delinearize_CDOUBLE_matrix(args[1], params.A, &r_out);
            } else {
                error_occurred = 1;
                nan_CDOUBLE_matrix(args[1], &r_out);
            }
        END_OUTER_LOOP
        release_zpotrf(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CDOUBLE_cholesky_lo(char **args, npy_intp const *dimensions, npy_intp const *steps,
                void *NPY_UNUSED(func))
{
    CDOUBLE_cholesky('L', args, dimensions, steps);
}



/* -------------------------------------------------------------------------- */
                          /* eig family  */

typedef struct geev_params_struct {
    void *A;
    void *WR; /* RWORK in complex versions, REAL W buffer for (sd)geev*/
    void *WI;
    void *VLR; /* REAL VL buffers for _geev where _ is s, d */
    void *VRR; /* REAL VR buffers for _geev where _ is s, d */
    void *WORK;
    void *W;  /* final w */
    void *VL; /* final vl */
    void *VR; /* final vr */

    fortran_int N;
    fortran_int LDA;
    fortran_int LDVL;
    fortran_int LDVR;
    fortran_int LWORK;

    char JOBVL;
    char JOBVR;
} GEEV_PARAMS_t;

static NPY_INLINE void
dump_geev_params(const char *name, GEEV_PARAMS_t* params)
{
    TRACE_TXT("\n%s\n"

              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\
              "\t%10s: %p\n"\

              "\t%10s: %d\n"\
              "\t%10s: %d\n"\
              "\t%10s: %d\n"\
              "\t%10s: %d\n"\
              "\t%10s: %d\n"\

              "\t%10s: %c\n"\
              "\t%10s: %c\n",

              name,

              "A", params->A,
              "WR", params->WR,
              "WI", params->WI,
              "VLR", params->VLR,
              "VRR", params->VRR,
              "WORK", params->WORK,
              "W", params->W,
              "VL", params->VL,
              "VR", params->VR,

              "N", (int)params->N,
              "LDA", (int)params->LDA,
              "LDVL", (int)params->LDVL,
              "LDVR", (int)params->LDVR,
              "LWORK", (int)params->LWORK,

              "JOBVL", params->JOBVL,
              "JOBVR", params->JOBVR);
}

#line 1914

static NPY_INLINE fortran_int
call_sgeev(GEEV_PARAMS_t* params)
{
    fortran_int rv;
    LAPACK(sgeev)(&params->JOBVL, &params->JOBVR,
                          &params->N, params->A, &params->LDA,
                          params->WR, params->WI,
                          params->VLR, &params->LDVL,
                          params->VRR, &params->LDVR,
                          params->WORK, &params->LWORK,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_sgeev(GEEV_PARAMS_t *params, char jobvl, char jobvr, fortran_int n)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *wr, *wi, *vlr, *vrr, *work, *w, *vl, *vr;
    size_t safe_n = n;
    size_t a_size = safe_n * safe_n * sizeof(float);
    size_t wr_size = safe_n * sizeof(float);
    size_t wi_size = safe_n * sizeof(float);
    size_t vlr_size = jobvl=='V' ? safe_n * safe_n * sizeof(float) : 0;
    size_t vrr_size = jobvr=='V' ? safe_n * safe_n * sizeof(float) : 0;
    size_t w_size = wr_size*2;
    size_t vl_size = vlr_size*2;
    size_t vr_size = vrr_size*2;
    size_t work_count = 0;
    fortran_int ld = fortran_int_max(n, 1);

    /* allocate data for known sizes (all but work) */
    mem_buff = malloc(a_size + wr_size + wi_size +
                      vlr_size + vrr_size +
                      w_size + vl_size + vr_size);
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    wr = a + a_size;
    wi = wr + wr_size;
    vlr = wi + wi_size;
    vrr = vlr + vlr_size;
    w = vrr + vrr_size;
    vl = w + w_size;
    vr = vl + vl_size;

    params->A = a;
    params->WR = wr;
    params->WI = wi;
    params->VLR = vlr;
    params->VRR = vrr;
    params->W = w;
    params->VL = vl;
    params->VR = vr;
    params->N = n;
    params->LDA = ld;
    params->LDVL = ld;
    params->LDVR = ld;
    params->JOBVL = jobvl;
    params->JOBVR = jobvr;

    /* Work size query */
    {
        float work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_sgeev(params) != 0) {
            goto error;
        }

        work_count = (size_t)work_size_query;
    }

    mem_buff2 = malloc(work_count*sizeof(float));
    if (!mem_buff2) {
        goto error;
    }
    work = mem_buff2;

    params->LWORK = (fortran_int)work_count;
    params->WORK = work;

    return 1;
 error:
    free(mem_buff2);
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
mk_FLOAT_complex_array_from_real(COMPLEX_t *c, const float *re, size_t n)
{
    size_t iter;
    for (iter = 0; iter < n; ++iter) {
        c[iter].array[0] = re[iter];
        c[iter].array[1] = 0.0f;
    }
}

static NPY_INLINE void
mk_FLOAT_complex_array(COMPLEX_t *c,
                        const float *re,
                        const float *im,
                        size_t n)
{
    size_t iter;
    for (iter = 0; iter < n; ++iter) {
        c[iter].array[0] = re[iter];
        c[iter].array[1] = im[iter];
    }
}

static NPY_INLINE void
mk_FLOAT_complex_array_conjugate_pair(COMPLEX_t *c,
                                       const float *r,
                                       size_t n)
{
    size_t iter;
    for (iter = 0; iter < n; ++iter) {
        float re = r[iter];
        float im = r[iter+n];
        c[iter].array[0] = re;
        c[iter].array[1] = im;
        c[iter+n].array[0] = re;
        c[iter+n].array[1] = -im;
    }
}

/*
 * make the complex eigenvectors from the real array produced by sgeev/zgeev.
 * c is the array where the results will be left.
 * r is the source array of reals produced by sgeev/zgeev
 * i is the eigenvalue imaginary part produced by sgeev/zgeev
 * n is so that the order of the matrix is n by n
 */
static NPY_INLINE void
mk_sgeev_complex_eigenvectors(COMPLEX_t *c,
                                      const float *r,
                                      const float *i,
                                      size_t n)
{
    size_t iter = 0;
    while (iter < n)
    {
        if (i[iter] ==  0.0f) {
            /* eigenvalue was real, eigenvectors as well...  */
            mk_FLOAT_complex_array_from_real(c, r, n);
            c += n;
            r += n;
            iter ++;
        } else {
            /* eigenvalue was complex, generate a pair of eigenvectors */
            mk_FLOAT_complex_array_conjugate_pair(c, r, n);
            c += 2*n;
            r += 2*n;
            iter += 2;
        }
    }
}


static NPY_INLINE void
process_sgeev_results(GEEV_PARAMS_t *params)
{
    /* REAL versions of geev need the results to be translated
     * into complex versions. This is the way to deal with imaginary
     * results. In our gufuncs we will always return complex arrays!
     */
    mk_FLOAT_complex_array(params->W, params->WR, params->WI, params->N);

    /* handle the eigenvectors */
    if ('V' == params->JOBVL) {
        mk_sgeev_complex_eigenvectors(params->VL, params->VLR,
                                              params->WI, params->N);
    }
    if ('V' == params->JOBVR) {
        mk_sgeev_complex_eigenvectors(params->VR, params->VRR,
                                              params->WI, params->N);
    }
}


#line 1914

static NPY_INLINE fortran_int
call_dgeev(GEEV_PARAMS_t* params)
{
    fortran_int rv;
    LAPACK(dgeev)(&params->JOBVL, &params->JOBVR,
                          &params->N, params->A, &params->LDA,
                          params->WR, params->WI,
                          params->VLR, &params->LDVL,
                          params->VRR, &params->LDVR,
                          params->WORK, &params->LWORK,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_dgeev(GEEV_PARAMS_t *params, char jobvl, char jobvr, fortran_int n)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *wr, *wi, *vlr, *vrr, *work, *w, *vl, *vr;
    size_t safe_n = n;
    size_t a_size = safe_n * safe_n * sizeof(double);
    size_t wr_size = safe_n * sizeof(double);
    size_t wi_size = safe_n * sizeof(double);
    size_t vlr_size = jobvl=='V' ? safe_n * safe_n * sizeof(double) : 0;
    size_t vrr_size = jobvr=='V' ? safe_n * safe_n * sizeof(double) : 0;
    size_t w_size = wr_size*2;
    size_t vl_size = vlr_size*2;
    size_t vr_size = vrr_size*2;
    size_t work_count = 0;
    fortran_int ld = fortran_int_max(n, 1);

    /* allocate data for known sizes (all but work) */
    mem_buff = malloc(a_size + wr_size + wi_size +
                      vlr_size + vrr_size +
                      w_size + vl_size + vr_size);
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    wr = a + a_size;
    wi = wr + wr_size;
    vlr = wi + wi_size;
    vrr = vlr + vlr_size;
    w = vrr + vrr_size;
    vl = w + w_size;
    vr = vl + vl_size;

    params->A = a;
    params->WR = wr;
    params->WI = wi;
    params->VLR = vlr;
    params->VRR = vrr;
    params->W = w;
    params->VL = vl;
    params->VR = vr;
    params->N = n;
    params->LDA = ld;
    params->LDVL = ld;
    params->LDVR = ld;
    params->JOBVL = jobvl;
    params->JOBVR = jobvr;

    /* Work size query */
    {
        double work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_dgeev(params) != 0) {
            goto error;
        }

        work_count = (size_t)work_size_query;
    }

    mem_buff2 = malloc(work_count*sizeof(double));
    if (!mem_buff2) {
        goto error;
    }
    work = mem_buff2;

    params->LWORK = (fortran_int)work_count;
    params->WORK = work;

    return 1;
 error:
    free(mem_buff2);
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

static NPY_INLINE void
mk_DOUBLE_complex_array_from_real(DOUBLECOMPLEX_t *c, const double *re, size_t n)
{
    size_t iter;
    for (iter = 0; iter < n; ++iter) {
        c[iter].array[0] = re[iter];
        c[iter].array[1] = 0.0;
    }
}

static NPY_INLINE void
mk_DOUBLE_complex_array(DOUBLECOMPLEX_t *c,
                        const double *re,
                        const double *im,
                        size_t n)
{
    size_t iter;
    for (iter = 0; iter < n; ++iter) {
        c[iter].array[0] = re[iter];
        c[iter].array[1] = im[iter];
    }
}

static NPY_INLINE void
mk_DOUBLE_complex_array_conjugate_pair(DOUBLECOMPLEX_t *c,
                                       const double *r,
                                       size_t n)
{
    size_t iter;
    for (iter = 0; iter < n; ++iter) {
        double re = r[iter];
        double im = r[iter+n];
        c[iter].array[0] = re;
        c[iter].array[1] = im;
        c[iter+n].array[0] = re;
        c[iter+n].array[1] = -im;
    }
}

/*
 * make the complex eigenvectors from the real array produced by sgeev/zgeev.
 * c is the array where the results will be left.
 * r is the source array of reals produced by sgeev/zgeev
 * i is the eigenvalue imaginary part produced by sgeev/zgeev
 * n is so that the order of the matrix is n by n
 */
static NPY_INLINE void
mk_dgeev_complex_eigenvectors(DOUBLECOMPLEX_t *c,
                                      const double *r,
                                      const double *i,
                                      size_t n)
{
    size_t iter = 0;
    while (iter < n)
    {
        if (i[iter] ==  0.0) {
            /* eigenvalue was real, eigenvectors as well...  */
            mk_DOUBLE_complex_array_from_real(c, r, n);
            c += n;
            r += n;
            iter ++;
        } else {
            /* eigenvalue was complex, generate a pair of eigenvectors */
            mk_DOUBLE_complex_array_conjugate_pair(c, r, n);
            c += 2*n;
            r += 2*n;
            iter += 2;
        }
    }
}


static NPY_INLINE void
process_dgeev_results(GEEV_PARAMS_t *params)
{
    /* REAL versions of geev need the results to be translated
     * into complex versions. This is the way to deal with imaginary
     * results. In our gufuncs we will always return complex arrays!
     */
    mk_DOUBLE_complex_array(params->W, params->WR, params->WI, params->N);

    /* handle the eigenvectors */
    if ('V' == params->JOBVL) {
        mk_dgeev_complex_eigenvectors(params->VL, params->VLR,
                                              params->WI, params->N);
    }
    if ('V' == params->JOBVR) {
        mk_dgeev_complex_eigenvectors(params->VR, params->VRR,
                                              params->WI, params->N);
    }
}




#line 2113

static NPY_INLINE fortran_int
call_cgeev(GEEV_PARAMS_t* params)
{
    fortran_int rv;

    LAPACK(cgeev)(&params->JOBVL, &params->JOBVR,
                          &params->N, params->A, &params->LDA,
                          params->W,
                          params->VL, &params->LDVL,
                          params->VR, &params->LDVR,
                          params->WORK, &params->LWORK,
                          params->WR, /* actually RWORK */
                          &rv);
    return rv;
}

static NPY_INLINE int
init_cgeev(GEEV_PARAMS_t* params,
                   char jobvl,
                   char jobvr,
                   fortran_int n)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *w, *vl, *vr, *work, *rwork;
    size_t safe_n = n;
    size_t a_size = safe_n * safe_n * sizeof(fortran_complex);
    size_t w_size = safe_n * sizeof(fortran_complex);
    size_t vl_size = jobvl=='V'? safe_n * safe_n * sizeof(fortran_complex) : 0;
    size_t vr_size = jobvr=='V'? safe_n * safe_n * sizeof(fortran_complex) : 0;
    size_t rwork_size = 2 * safe_n * sizeof(float);
    size_t work_count = 0;
    size_t total_size = a_size + w_size + vl_size + vr_size + rwork_size;
    fortran_int ld = fortran_int_max(n, 1);

    mem_buff = malloc(total_size);
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    w = a + a_size;
    vl = w + w_size;
    vr = vl + vl_size;
    rwork = vr + vr_size;

    params->A = a;
    params->WR = rwork;
    params->WI = NULL;
    params->VLR = NULL;
    params->VRR = NULL;
    params->VL = vl;
    params->VR = vr;
    params->W = w;
    params->N = n;
    params->LDA = ld;
    params->LDVL = ld;
    params->LDVR = ld;
    params->JOBVL = jobvl;
    params->JOBVR = jobvr;

    /* Work size query */
    {
        COMPLEX_t work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_cgeev(params) != 0) {
            goto error;
        }

        work_count = (size_t) work_size_query.array[0];
        /* Fix a bug in lapack 3.0.0 */
        if(work_count == 0) work_count = 1;
    }

    mem_buff2 = malloc(work_count*sizeof(fortran_complex));
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;

    params->LWORK = (fortran_int)work_count;
    params->WORK = work;

    return 1;
 error:
    free(mem_buff2);
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}


static NPY_INLINE void
process_cgeev_results(GEEV_PARAMS_t *NPY_UNUSED(params))
{
    /* nothing to do here, complex versions are ready to copy out */
}

#line 2113

static NPY_INLINE fortran_int
call_zgeev(GEEV_PARAMS_t* params)
{
    fortran_int rv;

    LAPACK(zgeev)(&params->JOBVL, &params->JOBVR,
                          &params->N, params->A, &params->LDA,
                          params->W,
                          params->VL, &params->LDVL,
                          params->VR, &params->LDVR,
                          params->WORK, &params->LWORK,
                          params->WR, /* actually RWORK */
                          &rv);
    return rv;
}

static NPY_INLINE int
init_zgeev(GEEV_PARAMS_t* params,
                   char jobvl,
                   char jobvr,
                   fortran_int n)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *w, *vl, *vr, *work, *rwork;
    size_t safe_n = n;
    size_t a_size = safe_n * safe_n * sizeof(fortran_doublecomplex);
    size_t w_size = safe_n * sizeof(fortran_doublecomplex);
    size_t vl_size = jobvl=='V'? safe_n * safe_n * sizeof(fortran_doublecomplex) : 0;
    size_t vr_size = jobvr=='V'? safe_n * safe_n * sizeof(fortran_doublecomplex) : 0;
    size_t rwork_size = 2 * safe_n * sizeof(double);
    size_t work_count = 0;
    size_t total_size = a_size + w_size + vl_size + vr_size + rwork_size;
    fortran_int ld = fortran_int_max(n, 1);

    mem_buff = malloc(total_size);
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    w = a + a_size;
    vl = w + w_size;
    vr = vl + vl_size;
    rwork = vr + vr_size;

    params->A = a;
    params->WR = rwork;
    params->WI = NULL;
    params->VLR = NULL;
    params->VRR = NULL;
    params->VL = vl;
    params->VR = vr;
    params->W = w;
    params->N = n;
    params->LDA = ld;
    params->LDVL = ld;
    params->LDVR = ld;
    params->JOBVL = jobvl;
    params->JOBVR = jobvr;

    /* Work size query */
    {
        DOUBLECOMPLEX_t work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_zgeev(params) != 0) {
            goto error;
        }

        work_count = (size_t) work_size_query.array[0];
        /* Fix a bug in lapack 3.0.0 */
        if(work_count == 0) work_count = 1;
    }

    mem_buff2 = malloc(work_count*sizeof(fortran_doublecomplex));
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;

    params->LWORK = (fortran_int)work_count;
    params->WORK = work;

    return 1;
 error:
    free(mem_buff2);
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}


static NPY_INLINE void
process_zgeev_results(GEEV_PARAMS_t *NPY_UNUSED(params))
{
    /* nothing to do here, complex versions are ready to copy out */
}



#line 2225

static NPY_INLINE void
release_sgeev(GEEV_PARAMS_t *params)
{
    free(params->WORK);
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
FLOAT_eig_wrapper(char JOBVL,
                   char JOBVR,
                   char**args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = 2;
    int error_occurred = get_fp_invalid_and_clear();
    GEEV_PARAMS_t geev_params;

    assert(JOBVL == 'N');

    STACK_TRACE;
    op_count += 'V'==JOBVL?1:0;
    op_count += 'V'==JOBVR?1:0;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_sgeev(&geev_params,
                           JOBVL, JOBVR,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t a_in;
        LINEARIZE_DATA_t w_out;
        LINEARIZE_DATA_t vl_out;
        LINEARIZE_DATA_t vr_out;

        init_linearize_data(&a_in,
                            geev_params.N, geev_params.N,
                            steps[1], steps[0]);
        steps += 2;
        init_linearize_data(&w_out,
                            1, geev_params.N,
                            0, steps[0]);
        steps += 1;
        if ('V' == geev_params.JOBVL) {
            init_linearize_data(&vl_out,
                                geev_params.N, geev_params.N,
                                steps[1], steps[0]);
            steps += 2;
        }
        if ('V' == geev_params.JOBVR) {
            init_linearize_data(&vr_out,
                                geev_params.N, geev_params.N,
                                steps[1], steps[0]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            char **arg_iter = args;
            /* copy the matrix in */
            linearize_FLOAT_matrix(geev_params.A, *arg_iter++, &a_in);
            not_ok = call_sgeev(&geev_params);

            if (!not_ok) {
                process_sgeev_results(&geev_params);
                delinearize_CFLOAT_matrix(*arg_iter++,
                                                 geev_params.W,
                                                 &w_out);

                if ('V' == geev_params.JOBVL) {
                    delinearize_CFLOAT_matrix(*arg_iter++,
                                                     geev_params.VL,
                                                     &vl_out);
                }
                if ('V' == geev_params.JOBVR) {
                    delinearize_CFLOAT_matrix(*arg_iter++,
                                                     geev_params.VR,
                                                     &vr_out);
                }
            } else {
                /* geev failed */
                error_occurred = 1;
                nan_CFLOAT_matrix(*arg_iter++, &w_out);
                if ('V' == geev_params.JOBVL) {
                    nan_CFLOAT_matrix(*arg_iter++, &vl_out);
                }
                if ('V' == geev_params.JOBVR) {
                    nan_CFLOAT_matrix(*arg_iter++, &vr_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_sgeev(&geev_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
FLOAT_eig(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    FLOAT_eig_wrapper('N', 'V', args, dimensions, steps);
}

static void
FLOAT_eigvals(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    FLOAT_eig_wrapper('N', 'N', args, dimensions, steps);
}


#line 2225

static NPY_INLINE void
release_dgeev(GEEV_PARAMS_t *params)
{
    free(params->WORK);
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
DOUBLE_eig_wrapper(char JOBVL,
                   char JOBVR,
                   char**args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = 2;
    int error_occurred = get_fp_invalid_and_clear();
    GEEV_PARAMS_t geev_params;

    assert(JOBVL == 'N');

    STACK_TRACE;
    op_count += 'V'==JOBVL?1:0;
    op_count += 'V'==JOBVR?1:0;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_dgeev(&geev_params,
                           JOBVL, JOBVR,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t a_in;
        LINEARIZE_DATA_t w_out;
        LINEARIZE_DATA_t vl_out;
        LINEARIZE_DATA_t vr_out;

        init_linearize_data(&a_in,
                            geev_params.N, geev_params.N,
                            steps[1], steps[0]);
        steps += 2;
        init_linearize_data(&w_out,
                            1, geev_params.N,
                            0, steps[0]);
        steps += 1;
        if ('V' == geev_params.JOBVL) {
            init_linearize_data(&vl_out,
                                geev_params.N, geev_params.N,
                                steps[1], steps[0]);
            steps += 2;
        }
        if ('V' == geev_params.JOBVR) {
            init_linearize_data(&vr_out,
                                geev_params.N, geev_params.N,
                                steps[1], steps[0]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            char **arg_iter = args;
            /* copy the matrix in */
            linearize_DOUBLE_matrix(geev_params.A, *arg_iter++, &a_in);
            not_ok = call_dgeev(&geev_params);

            if (!not_ok) {
                process_dgeev_results(&geev_params);
                delinearize_CDOUBLE_matrix(*arg_iter++,
                                                 geev_params.W,
                                                 &w_out);

                if ('V' == geev_params.JOBVL) {
                    delinearize_CDOUBLE_matrix(*arg_iter++,
                                                     geev_params.VL,
                                                     &vl_out);
                }
                if ('V' == geev_params.JOBVR) {
                    delinearize_CDOUBLE_matrix(*arg_iter++,
                                                     geev_params.VR,
                                                     &vr_out);
                }
            } else {
                /* geev failed */
                error_occurred = 1;
                nan_CDOUBLE_matrix(*arg_iter++, &w_out);
                if ('V' == geev_params.JOBVL) {
                    nan_CDOUBLE_matrix(*arg_iter++, &vl_out);
                }
                if ('V' == geev_params.JOBVR) {
                    nan_CDOUBLE_matrix(*arg_iter++, &vr_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_dgeev(&geev_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
DOUBLE_eig(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    DOUBLE_eig_wrapper('N', 'V', args, dimensions, steps);
}

static void
DOUBLE_eigvals(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    DOUBLE_eig_wrapper('N', 'N', args, dimensions, steps);
}


#line 2225

static NPY_INLINE void
release_zgeev(GEEV_PARAMS_t *params)
{
    free(params->WORK);
    free(params->A);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
CDOUBLE_eig_wrapper(char JOBVL,
                   char JOBVR,
                   char**args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = 2;
    int error_occurred = get_fp_invalid_and_clear();
    GEEV_PARAMS_t geev_params;

    assert(JOBVL == 'N');

    STACK_TRACE;
    op_count += 'V'==JOBVL?1:0;
    op_count += 'V'==JOBVR?1:0;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_zgeev(&geev_params,
                           JOBVL, JOBVR,
                           (fortran_int)dimensions[0])) {
        LINEARIZE_DATA_t a_in;
        LINEARIZE_DATA_t w_out;
        LINEARIZE_DATA_t vl_out;
        LINEARIZE_DATA_t vr_out;

        init_linearize_data(&a_in,
                            geev_params.N, geev_params.N,
                            steps[1], steps[0]);
        steps += 2;
        init_linearize_data(&w_out,
                            1, geev_params.N,
                            0, steps[0]);
        steps += 1;
        if ('V' == geev_params.JOBVL) {
            init_linearize_data(&vl_out,
                                geev_params.N, geev_params.N,
                                steps[1], steps[0]);
            steps += 2;
        }
        if ('V' == geev_params.JOBVR) {
            init_linearize_data(&vr_out,
                                geev_params.N, geev_params.N,
                                steps[1], steps[0]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            char **arg_iter = args;
            /* copy the matrix in */
            linearize_CDOUBLE_matrix(geev_params.A, *arg_iter++, &a_in);
            not_ok = call_zgeev(&geev_params);

            if (!not_ok) {
                process_zgeev_results(&geev_params);
                delinearize_CDOUBLE_matrix(*arg_iter++,
                                                 geev_params.W,
                                                 &w_out);

                if ('V' == geev_params.JOBVL) {
                    delinearize_CDOUBLE_matrix(*arg_iter++,
                                                     geev_params.VL,
                                                     &vl_out);
                }
                if ('V' == geev_params.JOBVR) {
                    delinearize_CDOUBLE_matrix(*arg_iter++,
                                                     geev_params.VR,
                                                     &vr_out);
                }
            } else {
                /* geev failed */
                error_occurred = 1;
                nan_CDOUBLE_matrix(*arg_iter++, &w_out);
                if ('V' == geev_params.JOBVL) {
                    nan_CDOUBLE_matrix(*arg_iter++, &vl_out);
                }
                if ('V' == geev_params.JOBVR) {
                    nan_CDOUBLE_matrix(*arg_iter++, &vr_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_zgeev(&geev_params);
    }

    set_fp_invalid_or_clear(error_occurred);
}

static void
CDOUBLE_eig(char **args,
           npy_intp const *dimensions,
           npy_intp const *steps,
           void *NPY_UNUSED(func))
{
    CDOUBLE_eig_wrapper('N', 'V', args, dimensions, steps);
}

static void
CDOUBLE_eigvals(char **args,
               npy_intp const *dimensions,
               npy_intp const *steps,
               void *NPY_UNUSED(func))
{
    CDOUBLE_eig_wrapper('N', 'N', args, dimensions, steps);
}




/* -------------------------------------------------------------------------- */
                 /* singular value decomposition  */

typedef struct gessd_params_struct
{
    void *A;
    void *S;
    void *U;
    void *VT;
    void *WORK;
    void *RWORK;
    void *IWORK;

    fortran_int M;
    fortran_int N;
    fortran_int LDA;
    fortran_int LDU;
    fortran_int LDVT;
    fortran_int LWORK;
    char JOBZ;
} GESDD_PARAMS_t;


static NPY_INLINE void
dump_gesdd_params(const char *name,
                  GESDD_PARAMS_t *params)
{
    TRACE_TXT("\n%s:\n"\

              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\

              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\

              "%14s: %15c'%c'\n",

              name,

              "A", params->A,
              "S", params->S,
              "U", params->U,
              "VT", params->VT,
              "WORK", params->WORK,
              "RWORK", params->RWORK,
              "IWORK", params->IWORK,

              "M", (int)params->M,
              "N", (int)params->N,
              "LDA", (int)params->LDA,
              "LDU", (int)params->LDU,
              "LDVT", (int)params->LDVT,
              "LWORK", (int)params->LWORK,

              "JOBZ", ' ', params->JOBZ);
}

static NPY_INLINE int
compute_urows_vtcolumns(char jobz,
                        fortran_int m, fortran_int n,
                        fortran_int *urows, fortran_int *vtcolumns)
{
    fortran_int min_m_n = fortran_int_min(m, n);
    switch(jobz)
    {
    case 'N':
        *urows = 0;
        *vtcolumns = 0;
        break;
    case 'A':
        *urows = m;
        *vtcolumns = n;
        break;
    case 'S':
        {
            *urows = min_m_n;
            *vtcolumns = min_m_n;
        }
        break;
    default:
        return 0;
    }

    return 1;
}


#line 2452

static NPY_INLINE fortran_int
call_sgesdd(GESDD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(sgesdd)(&params->JOBZ, &params->M, &params->N,
                          params->A, &params->LDA,
                          params->S,
                          params->U, &params->LDU,
                          params->VT, &params->LDVT,
                          params->WORK, &params->LWORK,
                          params->IWORK,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_sgesdd(GESDD_PARAMS_t *params,
                   char jobz,
                   fortran_int m,
                   fortran_int n)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *s, *u, *vt, *work, *iwork;
    size_t safe_m = m;
    size_t safe_n = n;
    size_t a_size = safe_m * safe_n * sizeof(fortran_real);
    fortran_int min_m_n = fortran_int_min(m, n);
    size_t safe_min_m_n = min_m_n;
    size_t s_size = safe_min_m_n * sizeof(fortran_real);
    fortran_int u_row_count, vt_column_count;
    size_t safe_u_row_count, safe_vt_column_count;
    size_t u_size, vt_size;
    fortran_int work_count;
    size_t work_size;
    size_t iwork_size = 8 * safe_min_m_n * sizeof(fortran_int);
    fortran_int ld = fortran_int_max(m, 1);

    if (!compute_urows_vtcolumns(jobz, m, n, &u_row_count, &vt_column_count)) {
        goto error;
    }

    safe_u_row_count = u_row_count;
    safe_vt_column_count = vt_column_count;

    u_size = safe_u_row_count * safe_m * sizeof(fortran_real);
    vt_size = safe_n * safe_vt_column_count * sizeof(fortran_real);

    mem_buff = malloc(a_size + s_size + u_size + vt_size + iwork_size);

    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    s = a + a_size;
    u = s + s_size;
    vt = u + u_size;
    iwork = vt + vt_size;

    /* fix vt_column_count so that it is a valid lapack parameter (0 is not) */
    vt_column_count = fortran_int_max(1, vt_column_count);

    params->M = m;
    params->N = n;
    params->A = a;
    params->S = s;
    params->U = u;
    params->VT = vt;
    params->RWORK = NULL;
    params->IWORK = iwork;
    params->LDA = ld;
    params->LDU = ld;
    params->LDVT = vt_column_count;
    params->JOBZ = jobz;

    /* Work size query */
    {
        fortran_real work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_sgesdd(params) != 0) {
            goto error;
        }

        work_count = (fortran_int)work_size_query;
        /* Fix a bug in lapack 3.0.0 */
        if(work_count == 0) work_count = 1;
        work_size = (size_t)work_count * sizeof(fortran_real);
    }

    mem_buff2 = malloc(work_size);
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;

    params->LWORK = work_count;
    params->WORK = work;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff);
    free(mem_buff2);
    memset(params, 0, sizeof(*params));

    return 0;
}


#line 2452

static NPY_INLINE fortran_int
call_dgesdd(GESDD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(dgesdd)(&params->JOBZ, &params->M, &params->N,
                          params->A, &params->LDA,
                          params->S,
                          params->U, &params->LDU,
                          params->VT, &params->LDVT,
                          params->WORK, &params->LWORK,
                          params->IWORK,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_dgesdd(GESDD_PARAMS_t *params,
                   char jobz,
                   fortran_int m,
                   fortran_int n)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *s, *u, *vt, *work, *iwork;
    size_t safe_m = m;
    size_t safe_n = n;
    size_t a_size = safe_m * safe_n * sizeof(fortran_doublereal);
    fortran_int min_m_n = fortran_int_min(m, n);
    size_t safe_min_m_n = min_m_n;
    size_t s_size = safe_min_m_n * sizeof(fortran_doublereal);
    fortran_int u_row_count, vt_column_count;
    size_t safe_u_row_count, safe_vt_column_count;
    size_t u_size, vt_size;
    fortran_int work_count;
    size_t work_size;
    size_t iwork_size = 8 * safe_min_m_n * sizeof(fortran_int);
    fortran_int ld = fortran_int_max(m, 1);

    if (!compute_urows_vtcolumns(jobz, m, n, &u_row_count, &vt_column_count)) {
        goto error;
    }

    safe_u_row_count = u_row_count;
    safe_vt_column_count = vt_column_count;

    u_size = safe_u_row_count * safe_m * sizeof(fortran_doublereal);
    vt_size = safe_n * safe_vt_column_count * sizeof(fortran_doublereal);

    mem_buff = malloc(a_size + s_size + u_size + vt_size + iwork_size);

    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    s = a + a_size;
    u = s + s_size;
    vt = u + u_size;
    iwork = vt + vt_size;

    /* fix vt_column_count so that it is a valid lapack parameter (0 is not) */
    vt_column_count = fortran_int_max(1, vt_column_count);

    params->M = m;
    params->N = n;
    params->A = a;
    params->S = s;
    params->U = u;
    params->VT = vt;
    params->RWORK = NULL;
    params->IWORK = iwork;
    params->LDA = ld;
    params->LDU = ld;
    params->LDVT = vt_column_count;
    params->JOBZ = jobz;

    /* Work size query */
    {
        fortran_doublereal work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_dgesdd(params) != 0) {
            goto error;
        }

        work_count = (fortran_int)work_size_query;
        /* Fix a bug in lapack 3.0.0 */
        if(work_count == 0) work_count = 1;
        work_size = (size_t)work_count * sizeof(fortran_doublereal);
    }

    mem_buff2 = malloc(work_size);
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;

    params->LWORK = work_count;
    params->WORK = work;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff);
    free(mem_buff2);
    memset(params, 0, sizeof(*params));

    return 0;
}



#line 2575

static NPY_INLINE fortran_int
call_cgesdd(GESDD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(cgesdd)(&params->JOBZ, &params->M, &params->N,
                          params->A, &params->LDA,
                          params->S,
                          params->U, &params->LDU,
                          params->VT, &params->LDVT,
                          params->WORK, &params->LWORK,
                          params->RWORK,
                          params->IWORK,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_cgesdd(GESDD_PARAMS_t *params,
                   char jobz,
                   fortran_int m,
                   fortran_int n)
{
    npy_uint8 *mem_buff = NULL, *mem_buff2 = NULL;
    npy_uint8 *a,*s, *u, *vt, *work, *rwork, *iwork;
    size_t a_size, s_size, u_size, vt_size, work_size, rwork_size, iwork_size;
    size_t safe_u_row_count, safe_vt_column_count;
    fortran_int u_row_count, vt_column_count, work_count;
    size_t safe_m = m;
    size_t safe_n = n;
    fortran_int min_m_n = fortran_int_min(m, n);
    size_t safe_min_m_n = min_m_n;
    fortran_int ld = fortran_int_max(m, 1);

    if (!compute_urows_vtcolumns(jobz, m, n, &u_row_count, &vt_column_count)) {
        goto error;
    }

    safe_u_row_count = u_row_count;
    safe_vt_column_count = vt_column_count;

    a_size = safe_m * safe_n * sizeof(fortran_complex);
    s_size = safe_min_m_n * sizeof(fortran_real);
    u_size = safe_u_row_count * safe_m * sizeof(fortran_complex);
    vt_size = safe_n * safe_vt_column_count * sizeof(fortran_complex);
    rwork_size = 'N'==jobz?
        (7 * safe_min_m_n) :
        (5*safe_min_m_n * safe_min_m_n + 5*safe_min_m_n);
    rwork_size *= sizeof(fortran_complex);
    iwork_size = 8 * safe_min_m_n* sizeof(fortran_int);

    mem_buff = malloc(a_size +
                      s_size +
                      u_size +
                      vt_size +
                      rwork_size +
                      iwork_size);
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    s = a + a_size;
    u = s + s_size;
    vt = u + u_size;
    rwork = vt + vt_size;
    iwork = rwork + rwork_size;

    /* fix vt_column_count so that it is a valid lapack parameter (0 is not) */
    vt_column_count = fortran_int_max(1, vt_column_count);

    params->A = a;
    params->S = s;
    params->U = u;
    params->VT = vt;
    params->RWORK = rwork;
    params->IWORK = iwork;
    params->M = m;
    params->N = n;
    params->LDA = ld;
    params->LDU = ld;
    params->LDVT = vt_column_count;
    params->JOBZ = jobz;

    /* Work size query */
    {
        fortran_complex work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_cgesdd(params) != 0) {
            goto error;
        }

        work_count = (fortran_int)((COMPLEX_t*)&work_size_query)->array[0];
        /* Fix a bug in lapack 3.0.0 */
        if(work_count == 0) work_count = 1;
        work_size = (size_t)work_count * sizeof(fortran_complex);
    }

    mem_buff2 = malloc(work_size);
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;

    params->LWORK = work_count;
    params->WORK = work;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff2);
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}

#line 2575

static NPY_INLINE fortran_int
call_zgesdd(GESDD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(zgesdd)(&params->JOBZ, &params->M, &params->N,
                          params->A, &params->LDA,
                          params->S,
                          params->U, &params->LDU,
                          params->VT, &params->LDVT,
                          params->WORK, &params->LWORK,
                          params->RWORK,
                          params->IWORK,
                          &rv);
    return rv;
}

static NPY_INLINE int
init_zgesdd(GESDD_PARAMS_t *params,
                   char jobz,
                   fortran_int m,
                   fortran_int n)
{
    npy_uint8 *mem_buff = NULL, *mem_buff2 = NULL;
    npy_uint8 *a,*s, *u, *vt, *work, *rwork, *iwork;
    size_t a_size, s_size, u_size, vt_size, work_size, rwork_size, iwork_size;
    size_t safe_u_row_count, safe_vt_column_count;
    fortran_int u_row_count, vt_column_count, work_count;
    size_t safe_m = m;
    size_t safe_n = n;
    fortran_int min_m_n = fortran_int_min(m, n);
    size_t safe_min_m_n = min_m_n;
    fortran_int ld = fortran_int_max(m, 1);

    if (!compute_urows_vtcolumns(jobz, m, n, &u_row_count, &vt_column_count)) {
        goto error;
    }

    safe_u_row_count = u_row_count;
    safe_vt_column_count = vt_column_count;

    a_size = safe_m * safe_n * sizeof(fortran_doublecomplex);
    s_size = safe_min_m_n * sizeof(fortran_doublereal);
    u_size = safe_u_row_count * safe_m * sizeof(fortran_doublecomplex);
    vt_size = safe_n * safe_vt_column_count * sizeof(fortran_doublecomplex);
    rwork_size = 'N'==jobz?
        (7 * safe_min_m_n) :
        (5*safe_min_m_n * safe_min_m_n + 5*safe_min_m_n);
    rwork_size *= sizeof(fortran_doublecomplex);
    iwork_size = 8 * safe_min_m_n* sizeof(fortran_int);

    mem_buff = malloc(a_size +
                      s_size +
                      u_size +
                      vt_size +
                      rwork_size +
                      iwork_size);
    if (!mem_buff) {
        goto error;
    }

    a = mem_buff;
    s = a + a_size;
    u = s + s_size;
    vt = u + u_size;
    rwork = vt + vt_size;
    iwork = rwork + rwork_size;

    /* fix vt_column_count so that it is a valid lapack parameter (0 is not) */
    vt_column_count = fortran_int_max(1, vt_column_count);

    params->A = a;
    params->S = s;
    params->U = u;
    params->VT = vt;
    params->RWORK = rwork;
    params->IWORK = iwork;
    params->M = m;
    params->N = n;
    params->LDA = ld;
    params->LDU = ld;
    params->LDVT = vt_column_count;
    params->JOBZ = jobz;

    /* Work size query */
    {
        fortran_doublecomplex work_size_query;

        params->LWORK = -1;
        params->WORK = &work_size_query;

        if (call_zgesdd(params) != 0) {
            goto error;
        }

        work_count = (fortran_int)((DOUBLECOMPLEX_t*)&work_size_query)->array[0];
        /* Fix a bug in lapack 3.0.0 */
        if(work_count == 0) work_count = 1;
        work_size = (size_t)work_count * sizeof(fortran_doublecomplex);
    }

    mem_buff2 = malloc(work_size);
    if (!mem_buff2) {
        goto error;
    }

    work = mem_buff2;

    params->LWORK = work_count;
    params->WORK = work;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff2);
    free(mem_buff);
    memset(params, 0, sizeof(*params));

    return 0;
}



#line 2703
static NPY_INLINE void
release_sgesdd(GESDD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
FLOAT_svd_wrapper(char JOBZ,
                   char **args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    int error_occurred = get_fp_invalid_and_clear();
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:4;
    GESDD_PARAMS_t params;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_sgesdd(&params,
                           JOBZ,
                           (fortran_int)dimensions[0],
                           (fortran_int)dimensions[1])) {
        LINEARIZE_DATA_t a_in, u_out, s_out, v_out;
        fortran_int min_m_n = params.M < params.N ? params.M : params.N;

        init_linearize_data(&a_in, params.N, params.M, steps[1], steps[0]);
        if ('N' == params.JOBZ) {
            /* only the singular values are wanted */
            init_linearize_data(&s_out, 1, min_m_n, 0, steps[2]);
        } else {
            fortran_int u_columns, v_rows;
            if ('S' == params.JOBZ) {
                u_columns = min_m_n;
                v_rows = min_m_n;
            } else { /* JOBZ == 'A' */
                u_columns = params.M;
                v_rows = params.N;
            }
            init_linearize_data(&u_out,
                                u_columns, params.M,
                                steps[3], steps[2]);
            init_linearize_data(&s_out,
                                1, min_m_n,
                                0, steps[4]);
            init_linearize_data(&v_out,
                                params.N, v_rows,
                                steps[6], steps[5]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_FLOAT_matrix(params.A, args[0], &a_in);
            not_ok = call_sgesdd(&params);
            if (!not_ok) {
                if ('N' == params.JOBZ) {
                    delinearize_FLOAT_matrix(args[1], params.S, &s_out);
                } else {
                    if ('A' == params.JOBZ && min_m_n == 0) {
                        /* Lapack has betrayed us and left these uninitialized,
                         * so produce an identity matrix for whichever of u
                         * and v is not empty.
                         */
                        identity_FLOAT_matrix(params.U, params.M);
                        identity_FLOAT_matrix(params.VT, params.N);
                    }

                    delinearize_FLOAT_matrix(args[1], params.U, &u_out);
                    delinearize_FLOAT_matrix(args[2], params.S, &s_out);
                    delinearize_FLOAT_matrix(args[3], params.VT, &v_out);
                }
            } else {
                error_occurred = 1;
                if ('N' == params.JOBZ) {
                    nan_FLOAT_matrix(args[1], &s_out);
                } else {
                    nan_FLOAT_matrix(args[1], &u_out);
                    nan_FLOAT_matrix(args[2], &s_out);
                    nan_FLOAT_matrix(args[3], &v_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_sgesdd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}
/**end repeat*/


/* svd gufunc entry points */
/**begin repeat
   #TYPE = FLOAT, DOUBLE, CFLOAT, CDOUBLE#
 */
static void
FLOAT_svd_N(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    FLOAT_svd_wrapper('N', args, dimensions, steps);
}

static void
FLOAT_svd_S(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    FLOAT_svd_wrapper('S', args, dimensions, steps);
}

static void
FLOAT_svd_A(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    FLOAT_svd_wrapper('A', args, dimensions, steps);
}


#line 2703
static NPY_INLINE void
release_dgesdd(GESDD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
DOUBLE_svd_wrapper(char JOBZ,
                   char **args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    int error_occurred = get_fp_invalid_and_clear();
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:4;
    GESDD_PARAMS_t params;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_dgesdd(&params,
                           JOBZ,
                           (fortran_int)dimensions[0],
                           (fortran_int)dimensions[1])) {
        LINEARIZE_DATA_t a_in, u_out, s_out, v_out;
        fortran_int min_m_n = params.M < params.N ? params.M : params.N;

        init_linearize_data(&a_in, params.N, params.M, steps[1], steps[0]);
        if ('N' == params.JOBZ) {
            /* only the singular values are wanted */
            init_linearize_data(&s_out, 1, min_m_n, 0, steps[2]);
        } else {
            fortran_int u_columns, v_rows;
            if ('S' == params.JOBZ) {
                u_columns = min_m_n;
                v_rows = min_m_n;
            } else { /* JOBZ == 'A' */
                u_columns = params.M;
                v_rows = params.N;
            }
            init_linearize_data(&u_out,
                                u_columns, params.M,
                                steps[3], steps[2]);
            init_linearize_data(&s_out,
                                1, min_m_n,
                                0, steps[4]);
            init_linearize_data(&v_out,
                                params.N, v_rows,
                                steps[6], steps[5]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_DOUBLE_matrix(params.A, args[0], &a_in);
            not_ok = call_dgesdd(&params);
            if (!not_ok) {
                if ('N' == params.JOBZ) {
                    delinearize_DOUBLE_matrix(args[1], params.S, &s_out);
                } else {
                    if ('A' == params.JOBZ && min_m_n == 0) {
                        /* Lapack has betrayed us and left these uninitialized,
                         * so produce an identity matrix for whichever of u
                         * and v is not empty.
                         */
                        identity_DOUBLE_matrix(params.U, params.M);
                        identity_DOUBLE_matrix(params.VT, params.N);
                    }

                    delinearize_DOUBLE_matrix(args[1], params.U, &u_out);
                    delinearize_DOUBLE_matrix(args[2], params.S, &s_out);
                    delinearize_DOUBLE_matrix(args[3], params.VT, &v_out);
                }
            } else {
                error_occurred = 1;
                if ('N' == params.JOBZ) {
                    nan_DOUBLE_matrix(args[1], &s_out);
                } else {
                    nan_DOUBLE_matrix(args[1], &u_out);
                    nan_DOUBLE_matrix(args[2], &s_out);
                    nan_DOUBLE_matrix(args[3], &v_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_dgesdd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}
/**end repeat*/


/* svd gufunc entry points */
/**begin repeat
   #TYPE = FLOAT, DOUBLE, CFLOAT, CDOUBLE#
 */
static void
DOUBLE_svd_N(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    DOUBLE_svd_wrapper('N', args, dimensions, steps);
}

static void
DOUBLE_svd_S(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    DOUBLE_svd_wrapper('S', args, dimensions, steps);
}

static void
DOUBLE_svd_A(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    DOUBLE_svd_wrapper('A', args, dimensions, steps);
}


#line 2703
static NPY_INLINE void
release_cgesdd(GESDD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
CFLOAT_svd_wrapper(char JOBZ,
                   char **args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    int error_occurred = get_fp_invalid_and_clear();
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:4;
    GESDD_PARAMS_t params;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_cgesdd(&params,
                           JOBZ,
                           (fortran_int)dimensions[0],
                           (fortran_int)dimensions[1])) {
        LINEARIZE_DATA_t a_in, u_out, s_out, v_out;
        fortran_int min_m_n = params.M < params.N ? params.M : params.N;

        init_linearize_data(&a_in, params.N, params.M, steps[1], steps[0]);
        if ('N' == params.JOBZ) {
            /* only the singular values are wanted */
            init_linearize_data(&s_out, 1, min_m_n, 0, steps[2]);
        } else {
            fortran_int u_columns, v_rows;
            if ('S' == params.JOBZ) {
                u_columns = min_m_n;
                v_rows = min_m_n;
            } else { /* JOBZ == 'A' */
                u_columns = params.M;
                v_rows = params.N;
            }
            init_linearize_data(&u_out,
                                u_columns, params.M,
                                steps[3], steps[2]);
            init_linearize_data(&s_out,
                                1, min_m_n,
                                0, steps[4]);
            init_linearize_data(&v_out,
                                params.N, v_rows,
                                steps[6], steps[5]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_CFLOAT_matrix(params.A, args[0], &a_in);
            not_ok = call_cgesdd(&params);
            if (!not_ok) {
                if ('N' == params.JOBZ) {
                    delinearize_FLOAT_matrix(args[1], params.S, &s_out);
                } else {
                    if ('A' == params.JOBZ && min_m_n == 0) {
                        /* Lapack has betrayed us and left these uninitialized,
                         * so produce an identity matrix for whichever of u
                         * and v is not empty.
                         */
                        identity_CFLOAT_matrix(params.U, params.M);
                        identity_CFLOAT_matrix(params.VT, params.N);
                    }

                    delinearize_CFLOAT_matrix(args[1], params.U, &u_out);
                    delinearize_FLOAT_matrix(args[2], params.S, &s_out);
                    delinearize_CFLOAT_matrix(args[3], params.VT, &v_out);
                }
            } else {
                error_occurred = 1;
                if ('N' == params.JOBZ) {
                    nan_FLOAT_matrix(args[1], &s_out);
                } else {
                    nan_CFLOAT_matrix(args[1], &u_out);
                    nan_FLOAT_matrix(args[2], &s_out);
                    nan_CFLOAT_matrix(args[3], &v_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_cgesdd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}
/**end repeat*/


/* svd gufunc entry points */
/**begin repeat
   #TYPE = FLOAT, DOUBLE, CFLOAT, CDOUBLE#
 */
static void
CFLOAT_svd_N(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    CFLOAT_svd_wrapper('N', args, dimensions, steps);
}

static void
CFLOAT_svd_S(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    CFLOAT_svd_wrapper('S', args, dimensions, steps);
}

static void
CFLOAT_svd_A(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    CFLOAT_svd_wrapper('A', args, dimensions, steps);
}


#line 2703
static NPY_INLINE void
release_zgesdd(GESDD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

static NPY_INLINE void
CDOUBLE_svd_wrapper(char JOBZ,
                   char **args,
                   npy_intp const *dimensions,
                   npy_intp const *steps)
{
    ptrdiff_t outer_steps[4];
    int error_occurred = get_fp_invalid_and_clear();
    size_t iter;
    size_t outer_dim = *dimensions++;
    size_t op_count = (JOBZ=='N')?2:4;
    GESDD_PARAMS_t params;

    for (iter = 0; iter < op_count; ++iter) {
        outer_steps[iter] = (ptrdiff_t) steps[iter];
    }
    steps += op_count;

    if (init_zgesdd(&params,
                           JOBZ,
                           (fortran_int)dimensions[0],
                           (fortran_int)dimensions[1])) {
        LINEARIZE_DATA_t a_in, u_out, s_out, v_out;
        fortran_int min_m_n = params.M < params.N ? params.M : params.N;

        init_linearize_data(&a_in, params.N, params.M, steps[1], steps[0]);
        if ('N' == params.JOBZ) {
            /* only the singular values are wanted */
            init_linearize_data(&s_out, 1, min_m_n, 0, steps[2]);
        } else {
            fortran_int u_columns, v_rows;
            if ('S' == params.JOBZ) {
                u_columns = min_m_n;
                v_rows = min_m_n;
            } else { /* JOBZ == 'A' */
                u_columns = params.M;
                v_rows = params.N;
            }
            init_linearize_data(&u_out,
                                u_columns, params.M,
                                steps[3], steps[2]);
            init_linearize_data(&s_out,
                                1, min_m_n,
                                0, steps[4]);
            init_linearize_data(&v_out,
                                params.N, v_rows,
                                steps[6], steps[5]);
        }

        for (iter = 0; iter < outer_dim; ++iter) {
            int not_ok;
            /* copy the matrix in */
            linearize_CDOUBLE_matrix(params.A, args[0], &a_in);
            not_ok = call_zgesdd(&params);
            if (!not_ok) {
                if ('N' == params.JOBZ) {
                    delinearize_DOUBLE_matrix(args[1], params.S, &s_out);
                } else {
                    if ('A' == params.JOBZ && min_m_n == 0) {
                        /* Lapack has betrayed us and left these uninitialized,
                         * so produce an identity matrix for whichever of u
                         * and v is not empty.
                         */
                        identity_CDOUBLE_matrix(params.U, params.M);
                        identity_CDOUBLE_matrix(params.VT, params.N);
                    }

                    delinearize_CDOUBLE_matrix(args[1], params.U, &u_out);
                    delinearize_DOUBLE_matrix(args[2], params.S, &s_out);
                    delinearize_CDOUBLE_matrix(args[3], params.VT, &v_out);
                }
            } else {
                error_occurred = 1;
                if ('N' == params.JOBZ) {
                    nan_DOUBLE_matrix(args[1], &s_out);
                } else {
                    nan_CDOUBLE_matrix(args[1], &u_out);
                    nan_DOUBLE_matrix(args[2], &s_out);
                    nan_CDOUBLE_matrix(args[3], &v_out);
                }
            }
            update_pointers((npy_uint8**)args, outer_steps, op_count);
        }

        release_zgesdd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}
/**end repeat*/


/* svd gufunc entry points */
/**begin repeat
   #TYPE = FLOAT, DOUBLE, CFLOAT, CDOUBLE#
 */
static void
CDOUBLE_svd_N(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    CDOUBLE_svd_wrapper('N', args, dimensions, steps);
}

static void
CDOUBLE_svd_S(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    CDOUBLE_svd_wrapper('S', args, dimensions, steps);
}

static void
CDOUBLE_svd_A(char **args,
             npy_intp const *dimensions,
             npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    CDOUBLE_svd_wrapper('A', args, dimensions, steps);
}




/* -------------------------------------------------------------------------- */
                 /* least squares */

typedef struct gelsd_params_struct
{
    fortran_int M;
    fortran_int N;
    fortran_int NRHS;
    void *A;
    fortran_int LDA;
    void *B;
    fortran_int LDB;
    void *S;
    void *RCOND;
    fortran_int RANK;
    void *WORK;
    fortran_int LWORK;
    void *RWORK;
    void *IWORK;
} GELSD_PARAMS_t;


static inline void
dump_gelsd_params(const char *name,
                  GELSD_PARAMS_t *params)
{
    TRACE_TXT("\n%s:\n"\

              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\
              "%14s: %18p\n"\

              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\
              "%14s: %18d\n"\

              "%14s: %18p\n",

              name,

              "A", params->A,
              "B", params->B,
              "S", params->S,
              "WORK", params->WORK,
              "RWORK", params->RWORK,
              "IWORK", params->IWORK,

              "M", (int)params->M,
              "N", (int)params->N,
              "NRHS", (int)params->NRHS,
              "LDA", (int)params->LDA,
              "LDB", (int)params->LDB,
              "LWORK", (int)params->LWORK,
              "RANK", (int)params->RANK,

              "RCOND", params->RCOND);
}


#line 2909

static inline fortran_int
call_sgelsd(GELSD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(sgelsd)(&params->M, &params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->B, &params->LDB,
                          params->S,
                          params->RCOND, &params->RANK,
                          params->WORK, &params->LWORK,
                          params->IWORK,
                          &rv);
    return rv;
}

static inline int
init_sgelsd(GELSD_PARAMS_t *params,
                   fortran_int m,
                   fortran_int n,
                   fortran_int nrhs)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *b, *s, *work, *iwork;
    fortran_int min_m_n = fortran_int_min(m, n);
    fortran_int max_m_n = fortran_int_max(m, n);
    size_t safe_min_m_n = min_m_n;
    size_t safe_max_m_n = max_m_n;
    size_t safe_m = m;
    size_t safe_n = n;
    size_t safe_nrhs = nrhs;

    size_t a_size = safe_m * safe_n * sizeof(fortran_real);
    size_t b_size = safe_max_m_n * safe_nrhs * sizeof(fortran_real);
    size_t s_size = safe_min_m_n * sizeof(fortran_real);

    fortran_int work_count;
    size_t work_size;
    size_t iwork_size;
    fortran_int lda = fortran_int_max(1, m);
    fortran_int ldb = fortran_int_max(1, fortran_int_max(m,n));

    mem_buff = malloc(a_size + b_size + s_size);

    if (!mem_buff)
        goto error;

    a = mem_buff;
    b = a + a_size;
    s = b + b_size;


    params->M = m;
    params->N = n;
    params->NRHS = nrhs;
    params->A = a;
    params->B = b;
    params->S = s;
    params->LDA = lda;
    params->LDB = ldb;

    {
        /* compute optimal work size */
        fortran_real work_size_query;
        fortran_int iwork_size_query;

        params->WORK = &work_size_query;
        params->IWORK = &iwork_size_query;
        params->RWORK = NULL;
        params->LWORK = -1;

        if (call_sgelsd(params) != 0)
            goto error;

        work_count = (fortran_int)work_size_query;

        work_size  = (size_t) work_size_query * sizeof(fortran_real);
        iwork_size = (size_t)iwork_size_query * sizeof(fortran_int);
    }

    mem_buff2 = malloc(work_size + iwork_size);
    if (!mem_buff2)
        goto error;

    work = mem_buff2;
    iwork = work + work_size;

    params->WORK = work;
    params->RWORK = NULL;
    params->IWORK = iwork;
    params->LWORK = work_count;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff);
    free(mem_buff2);
    memset(params, 0, sizeof(*params));

    return 0;
}


#line 2909

static inline fortran_int
call_dgelsd(GELSD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(dgelsd)(&params->M, &params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->B, &params->LDB,
                          params->S,
                          params->RCOND, &params->RANK,
                          params->WORK, &params->LWORK,
                          params->IWORK,
                          &rv);
    return rv;
}

static inline int
init_dgelsd(GELSD_PARAMS_t *params,
                   fortran_int m,
                   fortran_int n,
                   fortran_int nrhs)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *b, *s, *work, *iwork;
    fortran_int min_m_n = fortran_int_min(m, n);
    fortran_int max_m_n = fortran_int_max(m, n);
    size_t safe_min_m_n = min_m_n;
    size_t safe_max_m_n = max_m_n;
    size_t safe_m = m;
    size_t safe_n = n;
    size_t safe_nrhs = nrhs;

    size_t a_size = safe_m * safe_n * sizeof(fortran_doublereal);
    size_t b_size = safe_max_m_n * safe_nrhs * sizeof(fortran_doublereal);
    size_t s_size = safe_min_m_n * sizeof(fortran_doublereal);

    fortran_int work_count;
    size_t work_size;
    size_t iwork_size;
    fortran_int lda = fortran_int_max(1, m);
    fortran_int ldb = fortran_int_max(1, fortran_int_max(m,n));

    mem_buff = malloc(a_size + b_size + s_size);

    if (!mem_buff)
        goto error;

    a = mem_buff;
    b = a + a_size;
    s = b + b_size;


    params->M = m;
    params->N = n;
    params->NRHS = nrhs;
    params->A = a;
    params->B = b;
    params->S = s;
    params->LDA = lda;
    params->LDB = ldb;

    {
        /* compute optimal work size */
        fortran_doublereal work_size_query;
        fortran_int iwork_size_query;

        params->WORK = &work_size_query;
        params->IWORK = &iwork_size_query;
        params->RWORK = NULL;
        params->LWORK = -1;

        if (call_dgelsd(params) != 0)
            goto error;

        work_count = (fortran_int)work_size_query;

        work_size  = (size_t) work_size_query * sizeof(fortran_doublereal);
        iwork_size = (size_t)iwork_size_query * sizeof(fortran_int);
    }

    mem_buff2 = malloc(work_size + iwork_size);
    if (!mem_buff2)
        goto error;

    work = mem_buff2;
    iwork = work + work_size;

    params->WORK = work;
    params->RWORK = NULL;
    params->IWORK = iwork;
    params->LWORK = work_count;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff);
    free(mem_buff2);
    memset(params, 0, sizeof(*params));

    return 0;
}



#line 3021

static inline fortran_int
call_cgelsd(GELSD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(cgelsd)(&params->M, &params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->B, &params->LDB,
                          params->S,
                          params->RCOND, &params->RANK,
                          params->WORK, &params->LWORK,
                          params->RWORK, params->IWORK,
                          &rv);
    return rv;
}

static inline int
init_cgelsd(GELSD_PARAMS_t *params,
                   fortran_int m,
                   fortran_int n,
                   fortran_int nrhs)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *b, *s, *work, *iwork, *rwork;
    fortran_int min_m_n = fortran_int_min(m, n);
    fortran_int max_m_n = fortran_int_max(m, n);
    size_t safe_min_m_n = min_m_n;
    size_t safe_max_m_n = max_m_n;
    size_t safe_m = m;
    size_t safe_n = n;
    size_t safe_nrhs = nrhs;

    size_t a_size = safe_m * safe_n * sizeof(fortran_complex);
    size_t b_size = safe_max_m_n * safe_nrhs * sizeof(fortran_complex);
    size_t s_size = safe_min_m_n * sizeof(fortran_real);

    fortran_int work_count;
    size_t work_size, rwork_size, iwork_size;
    fortran_int lda = fortran_int_max(1, m);
    fortran_int ldb = fortran_int_max(1, fortran_int_max(m,n));

    mem_buff = malloc(a_size + b_size + s_size);

    if (!mem_buff)
        goto error;

    a = mem_buff;
    b = a + a_size;
    s = b + b_size;


    params->M = m;
    params->N = n;
    params->NRHS = nrhs;
    params->A = a;
    params->B = b;
    params->S = s;
    params->LDA = lda;
    params->LDB = ldb;

    {
        /* compute optimal work size */
        fortran_complex work_size_query;
        fortran_real rwork_size_query;
        fortran_int iwork_size_query;

        params->WORK = &work_size_query;
        params->IWORK = &iwork_size_query;
        params->RWORK = &rwork_size_query;
        params->LWORK = -1;

        if (call_cgelsd(params) != 0)
            goto error;

        work_count = (fortran_int)work_size_query.r;

        work_size  = (size_t )work_size_query.r * sizeof(fortran_complex);
        rwork_size = (size_t)rwork_size_query * sizeof(fortran_real);
        iwork_size = (size_t)iwork_size_query * sizeof(fortran_int);
    }

    mem_buff2 = malloc(work_size + rwork_size + iwork_size);
    if (!mem_buff2)
        goto error;

    work = mem_buff2;
    rwork = work + work_size;
    iwork = rwork + rwork_size;

    params->WORK = work;
    params->RWORK = rwork;
    params->IWORK = iwork;
    params->LWORK = work_count;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff);
    free(mem_buff2);
    memset(params, 0, sizeof(*params));

    return 0;
}


#line 3021

static inline fortran_int
call_zgelsd(GELSD_PARAMS_t *params)
{
    fortran_int rv;
    LAPACK(zgelsd)(&params->M, &params->N, &params->NRHS,
                          params->A, &params->LDA,
                          params->B, &params->LDB,
                          params->S,
                          params->RCOND, &params->RANK,
                          params->WORK, &params->LWORK,
                          params->RWORK, params->IWORK,
                          &rv);
    return rv;
}

static inline int
init_zgelsd(GELSD_PARAMS_t *params,
                   fortran_int m,
                   fortran_int n,
                   fortran_int nrhs)
{
    npy_uint8 *mem_buff = NULL;
    npy_uint8 *mem_buff2 = NULL;
    npy_uint8 *a, *b, *s, *work, *iwork, *rwork;
    fortran_int min_m_n = fortran_int_min(m, n);
    fortran_int max_m_n = fortran_int_max(m, n);
    size_t safe_min_m_n = min_m_n;
    size_t safe_max_m_n = max_m_n;
    size_t safe_m = m;
    size_t safe_n = n;
    size_t safe_nrhs = nrhs;

    size_t a_size = safe_m * safe_n * sizeof(fortran_doublecomplex);
    size_t b_size = safe_max_m_n * safe_nrhs * sizeof(fortran_doublecomplex);
    size_t s_size = safe_min_m_n * sizeof(fortran_doublereal);

    fortran_int work_count;
    size_t work_size, rwork_size, iwork_size;
    fortran_int lda = fortran_int_max(1, m);
    fortran_int ldb = fortran_int_max(1, fortran_int_max(m,n));

    mem_buff = malloc(a_size + b_size + s_size);

    if (!mem_buff)
        goto error;

    a = mem_buff;
    b = a + a_size;
    s = b + b_size;


    params->M = m;
    params->N = n;
    params->NRHS = nrhs;
    params->A = a;
    params->B = b;
    params->S = s;
    params->LDA = lda;
    params->LDB = ldb;

    {
        /* compute optimal work size */
        fortran_doublecomplex work_size_query;
        fortran_doublereal rwork_size_query;
        fortran_int iwork_size_query;

        params->WORK = &work_size_query;
        params->IWORK = &iwork_size_query;
        params->RWORK = &rwork_size_query;
        params->LWORK = -1;

        if (call_zgelsd(params) != 0)
            goto error;

        work_count = (fortran_int)work_size_query.r;

        work_size  = (size_t )work_size_query.r * sizeof(fortran_doublecomplex);
        rwork_size = (size_t)rwork_size_query * sizeof(fortran_doublereal);
        iwork_size = (size_t)iwork_size_query * sizeof(fortran_int);
    }

    mem_buff2 = malloc(work_size + rwork_size + iwork_size);
    if (!mem_buff2)
        goto error;

    work = mem_buff2;
    rwork = work + work_size;
    iwork = rwork + rwork_size;

    params->WORK = work;
    params->RWORK = rwork;
    params->IWORK = iwork;
    params->LWORK = work_count;

    return 1;
 error:
    TRACE_TXT("%s failed init\n", __FUNCTION__);
    free(mem_buff);
    free(mem_buff2);
    memset(params, 0, sizeof(*params));

    return 0;
}




#line 3140
static inline void
release_sgelsd(GELSD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

/** Compute the squared l2 norm of a contiguous vector */
static npy_float
FLOAT_abs2(npy_float *p, npy_intp n) {
    npy_intp i;
    npy_float res = 0;
    for (i = 0; i < n; i++) {
        npy_float el = p[i];
#if 0
        res += el.real*el.real + el.imag*el.imag;
#else
        res += el*el;
#endif
    }
    return res;
}

static void
FLOAT_lstsq(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GELSD_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n, m, nrhs;
    fortran_int excess;

    INIT_OUTER_LOOP_7

    m = (fortran_int)dimensions[0];
    n = (fortran_int)dimensions[1];
    nrhs = (fortran_int)dimensions[2];
    excess = m - n;

    if (init_sgelsd(&params, m, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, x_out, s_out, r_out;

        init_linearize_data(&a_in, n, m, steps[1], steps[0]);
        init_linearize_data_ex(&b_in, nrhs, m, steps[3], steps[2], fortran_int_max(n, m));
        init_linearize_data_ex(&x_out, nrhs, n, steps[5], steps[4], fortran_int_max(n, m));
        init_linearize_data(&r_out, 1, nrhs, 1, steps[6]);
        init_linearize_data(&s_out, 1, fortran_int_min(n, m), 1, steps[7]);

        BEGIN_OUTER_LOOP_7
            int not_ok;
            linearize_FLOAT_matrix(params.A, args[0], &a_in);
            linearize_FLOAT_matrix(params.B, args[1], &b_in);
            params.RCOND = args[2];
            not_ok = call_sgelsd(&params);
            if (!not_ok) {
                delinearize_FLOAT_matrix(args[3], params.B, &x_out);
                *(npy_int*) args[5] = params.RANK;
                delinearize_FLOAT_matrix(args[6], params.S, &s_out);

                /* Note that linalg.lstsq discards this when excess == 0 */
                if (excess >= 0 && params.RANK == n) {
                    /* Compute the residuals as the square sum of each column */
                    int i;
                    char *resid = args[4];
                    fortran_real *components = (fortran_real *)params.B + n;
                    for (i = 0; i < nrhs; i++) {
                        fortran_real *vector = components + i*m;
                        /* Numpy and fortran floating types are the same size,
                         * so this cast is safe */
                        npy_float abs2 = FLOAT_abs2((npy_float *)vector, excess);
                        memcpy(
                            resid + i*r_out.column_strides,
                            &abs2, sizeof(abs2));
                    }
                }
                else {
                    /* Note that this is always discarded by linalg.lstsq */
                    nan_FLOAT_matrix(args[4], &r_out);
                }
            } else {
                error_occurred = 1;
                nan_FLOAT_matrix(args[3], &x_out);
                nan_FLOAT_matrix(args[4], &r_out);
                *(npy_int*) args[5] = -1;
                nan_FLOAT_matrix(args[6], &s_out);
            }
        END_OUTER_LOOP

        release_sgelsd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}


#line 3140
static inline void
release_dgelsd(GELSD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

/** Compute the squared l2 norm of a contiguous vector */
static npy_double
DOUBLE_abs2(npy_double *p, npy_intp n) {
    npy_intp i;
    npy_double res = 0;
    for (i = 0; i < n; i++) {
        npy_double el = p[i];
#if 0
        res += el.real*el.real + el.imag*el.imag;
#else
        res += el*el;
#endif
    }
    return res;
}

static void
DOUBLE_lstsq(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GELSD_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n, m, nrhs;
    fortran_int excess;

    INIT_OUTER_LOOP_7

    m = (fortran_int)dimensions[0];
    n = (fortran_int)dimensions[1];
    nrhs = (fortran_int)dimensions[2];
    excess = m - n;

    if (init_dgelsd(&params, m, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, x_out, s_out, r_out;

        init_linearize_data(&a_in, n, m, steps[1], steps[0]);
        init_linearize_data_ex(&b_in, nrhs, m, steps[3], steps[2], fortran_int_max(n, m));
        init_linearize_data_ex(&x_out, nrhs, n, steps[5], steps[4], fortran_int_max(n, m));
        init_linearize_data(&r_out, 1, nrhs, 1, steps[6]);
        init_linearize_data(&s_out, 1, fortran_int_min(n, m), 1, steps[7]);

        BEGIN_OUTER_LOOP_7
            int not_ok;
            linearize_DOUBLE_matrix(params.A, args[0], &a_in);
            linearize_DOUBLE_matrix(params.B, args[1], &b_in);
            params.RCOND = args[2];
            not_ok = call_dgelsd(&params);
            if (!not_ok) {
                delinearize_DOUBLE_matrix(args[3], params.B, &x_out);
                *(npy_int*) args[5] = params.RANK;
                delinearize_DOUBLE_matrix(args[6], params.S, &s_out);

                /* Note that linalg.lstsq discards this when excess == 0 */
                if (excess >= 0 && params.RANK == n) {
                    /* Compute the residuals as the square sum of each column */
                    int i;
                    char *resid = args[4];
                    fortran_doublereal *components = (fortran_doublereal *)params.B + n;
                    for (i = 0; i < nrhs; i++) {
                        fortran_doublereal *vector = components + i*m;
                        /* Numpy and fortran floating types are the same size,
                         * so this cast is safe */
                        npy_double abs2 = DOUBLE_abs2((npy_double *)vector, excess);
                        memcpy(
                            resid + i*r_out.column_strides,
                            &abs2, sizeof(abs2));
                    }
                }
                else {
                    /* Note that this is always discarded by linalg.lstsq */
                    nan_DOUBLE_matrix(args[4], &r_out);
                }
            } else {
                error_occurred = 1;
                nan_DOUBLE_matrix(args[3], &x_out);
                nan_DOUBLE_matrix(args[4], &r_out);
                *(npy_int*) args[5] = -1;
                nan_DOUBLE_matrix(args[6], &s_out);
            }
        END_OUTER_LOOP

        release_dgelsd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}


#line 3140
static inline void
release_cgelsd(GELSD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

/** Compute the squared l2 norm of a contiguous vector */
static npy_float
CFLOAT_abs2(npy_cfloat *p, npy_intp n) {
    npy_intp i;
    npy_float res = 0;
    for (i = 0; i < n; i++) {
        npy_cfloat el = p[i];
#if 1
        res += el.real*el.real + el.imag*el.imag;
#else
        res += el*el;
#endif
    }
    return res;
}

static void
CFLOAT_lstsq(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GELSD_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n, m, nrhs;
    fortran_int excess;

    INIT_OUTER_LOOP_7

    m = (fortran_int)dimensions[0];
    n = (fortran_int)dimensions[1];
    nrhs = (fortran_int)dimensions[2];
    excess = m - n;

    if (init_cgelsd(&params, m, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, x_out, s_out, r_out;

        init_linearize_data(&a_in, n, m, steps[1], steps[0]);
        init_linearize_data_ex(&b_in, nrhs, m, steps[3], steps[2], fortran_int_max(n, m));
        init_linearize_data_ex(&x_out, nrhs, n, steps[5], steps[4], fortran_int_max(n, m));
        init_linearize_data(&r_out, 1, nrhs, 1, steps[6]);
        init_linearize_data(&s_out, 1, fortran_int_min(n, m), 1, steps[7]);

        BEGIN_OUTER_LOOP_7
            int not_ok;
            linearize_CFLOAT_matrix(params.A, args[0], &a_in);
            linearize_CFLOAT_matrix(params.B, args[1], &b_in);
            params.RCOND = args[2];
            not_ok = call_cgelsd(&params);
            if (!not_ok) {
                delinearize_CFLOAT_matrix(args[3], params.B, &x_out);
                *(npy_int*) args[5] = params.RANK;
                delinearize_FLOAT_matrix(args[6], params.S, &s_out);

                /* Note that linalg.lstsq discards this when excess == 0 */
                if (excess >= 0 && params.RANK == n) {
                    /* Compute the residuals as the square sum of each column */
                    int i;
                    char *resid = args[4];
                    fortran_complex *components = (fortran_complex *)params.B + n;
                    for (i = 0; i < nrhs; i++) {
                        fortran_complex *vector = components + i*m;
                        /* Numpy and fortran floating types are the same size,
                         * so this cast is safe */
                        npy_float abs2 = CFLOAT_abs2((npy_cfloat *)vector, excess);
                        memcpy(
                            resid + i*r_out.column_strides,
                            &abs2, sizeof(abs2));
                    }
                }
                else {
                    /* Note that this is always discarded by linalg.lstsq */
                    nan_FLOAT_matrix(args[4], &r_out);
                }
            } else {
                error_occurred = 1;
                nan_CFLOAT_matrix(args[3], &x_out);
                nan_FLOAT_matrix(args[4], &r_out);
                *(npy_int*) args[5] = -1;
                nan_FLOAT_matrix(args[6], &s_out);
            }
        END_OUTER_LOOP

        release_cgelsd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}


#line 3140
static inline void
release_zgelsd(GELSD_PARAMS_t* params)
{
    /* A and WORK contain allocated blocks */
    free(params->A);
    free(params->WORK);
    memset(params, 0, sizeof(*params));
}

/** Compute the squared l2 norm of a contiguous vector */
static npy_double
CDOUBLE_abs2(npy_cdouble *p, npy_intp n) {
    npy_intp i;
    npy_double res = 0;
    for (i = 0; i < n; i++) {
        npy_cdouble el = p[i];
#if 1
        res += el.real*el.real + el.imag*el.imag;
#else
        res += el*el;
#endif
    }
    return res;
}

static void
CDOUBLE_lstsq(char **args, npy_intp const *dimensions, npy_intp const *steps,
             void *NPY_UNUSED(func))
{
    GELSD_PARAMS_t params;
    int error_occurred = get_fp_invalid_and_clear();
    fortran_int n, m, nrhs;
    fortran_int excess;

    INIT_OUTER_LOOP_7

    m = (fortran_int)dimensions[0];
    n = (fortran_int)dimensions[1];
    nrhs = (fortran_int)dimensions[2];
    excess = m - n;

    if (init_zgelsd(&params, m, n, nrhs)) {
        LINEARIZE_DATA_t a_in, b_in, x_out, s_out, r_out;

        init_linearize_data(&a_in, n, m, steps[1], steps[0]);
        init_linearize_data_ex(&b_in, nrhs, m, steps[3], steps[2], fortran_int_max(n, m));
        init_linearize_data_ex(&x_out, nrhs, n, steps[5], steps[4], fortran_int_max(n, m));
        init_linearize_data(&r_out, 1, nrhs, 1, steps[6]);
        init_linearize_data(&s_out, 1, fortran_int_min(n, m), 1, steps[7]);

        BEGIN_OUTER_LOOP_7
            int not_ok;
            linearize_CDOUBLE_matrix(params.A, args[0], &a_in);
            linearize_CDOUBLE_matrix(params.B, args[1], &b_in);
            params.RCOND = args[2];
            not_ok = call_zgelsd(&params);
            if (!not_ok) {
                delinearize_CDOUBLE_matrix(args[3], params.B, &x_out);
                *(npy_int*) args[5] = params.RANK;
                delinearize_DOUBLE_matrix(args[6], params.S, &s_out);

                /* Note that linalg.lstsq discards this when excess == 0 */
                if (excess >= 0 && params.RANK == n) {
                    /* Compute the residuals as the square sum of each column */
                    int i;
                    char *resid = args[4];
                    fortran_doublecomplex *components = (fortran_doublecomplex *)params.B + n;
                    for (i = 0; i < nrhs; i++) {
                        fortran_doublecomplex *vector = components + i*m;
                        /* Numpy and fortran floating types are the same size,
                         * so this cast is safe */
                        npy_double abs2 = CDOUBLE_abs2((npy_cdouble *)vector, excess);
                        memcpy(
                            resid + i*r_out.column_strides,
                            &abs2, sizeof(abs2));
                    }
                }
                else {
                    /* Note that this is always discarded by linalg.lstsq */
                    nan_DOUBLE_matrix(args[4], &r_out);
                }
            } else {
                error_occurred = 1;
                nan_CDOUBLE_matrix(args[3], &x_out);
                nan_DOUBLE_matrix(args[4], &r_out);
                *(npy_int*) args[5] = -1;
                nan_DOUBLE_matrix(args[6], &s_out);
            }
        END_OUTER_LOOP

        release_zgelsd(&params);
    }

    set_fp_invalid_or_clear(error_occurred);
}



#pragma GCC diagnostic pop

/* -------------------------------------------------------------------------- */
              /* gufunc registration  */

static void *array_of_nulls[] = {
    (void *)NULL,
    (void *)NULL,
    (void *)NULL,
    (void *)NULL,

    (void *)NULL,
    (void *)NULL,
    (void *)NULL,
    (void *)NULL,

    (void *)NULL,
    (void *)NULL,
    (void *)NULL,
    (void *)NULL,

    (void *)NULL,
    (void *)NULL,
    (void *)NULL,
    (void *)NULL
};

#define FUNC_ARRAY_NAME(NAME) NAME ## _funcs

#define GUFUNC_FUNC_ARRAY_REAL(NAME)                    \
    static PyUFuncGenericFunction                       \
    FUNC_ARRAY_NAME(NAME)[] = {                         \
        FLOAT_ ## NAME,                                 \
        DOUBLE_ ## NAME                                 \
    }

#define GUFUNC_FUNC_ARRAY_REAL_COMPLEX(NAME)            \
    static PyUFuncGenericFunction                       \
    FUNC_ARRAY_NAME(NAME)[] = {                         \
        FLOAT_ ## NAME,                                 \
        DOUBLE_ ## NAME,                                \
        CFLOAT_ ## NAME,                                \
        CDOUBLE_ ## NAME                                \
    }

/* There are problems with eig in complex single precision.
 * That kernel is disabled
 */
#define GUFUNC_FUNC_ARRAY_EIG(NAME)                     \
    static PyUFuncGenericFunction                       \
    FUNC_ARRAY_NAME(NAME)[] = {                         \
        FLOAT_ ## NAME,                                 \
        DOUBLE_ ## NAME,                                \
        CDOUBLE_ ## NAME                                \
    }


GUFUNC_FUNC_ARRAY_REAL_COMPLEX(slogdet);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(det);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(eighlo);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(eighup);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(eigvalshlo);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(eigvalshup);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(solve);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(solve1);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(inv);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(cholesky_lo);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(svd_N);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(svd_S);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(svd_A);
GUFUNC_FUNC_ARRAY_REAL_COMPLEX(lstsq);
GUFUNC_FUNC_ARRAY_EIG(eig);
GUFUNC_FUNC_ARRAY_EIG(eigvals);

static char equal_2_types[] = {
    NPY_FLOAT, NPY_FLOAT,
    NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT, NPY_CFLOAT,
    NPY_CDOUBLE, NPY_CDOUBLE
};

static char equal_3_types[] = {
    NPY_FLOAT, NPY_FLOAT, NPY_FLOAT,
    NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT, NPY_CFLOAT, NPY_CFLOAT,
    NPY_CDOUBLE, NPY_CDOUBLE, NPY_CDOUBLE
};

/* second result is logdet, that will always be a REAL */
static char slogdet_types[] = {
    NPY_FLOAT, NPY_FLOAT, NPY_FLOAT,
    NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT, NPY_CFLOAT, NPY_FLOAT,
    NPY_CDOUBLE, NPY_CDOUBLE, NPY_DOUBLE
};

static char eigh_types[] = {
    NPY_FLOAT, NPY_FLOAT, NPY_FLOAT,
    NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT, NPY_FLOAT, NPY_CFLOAT,
    NPY_CDOUBLE, NPY_DOUBLE, NPY_CDOUBLE
};

static char eighvals_types[] = {
    NPY_FLOAT, NPY_FLOAT,
    NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT, NPY_FLOAT,
    NPY_CDOUBLE, NPY_DOUBLE
};

static char eig_types[] = {
    NPY_FLOAT, NPY_CFLOAT, NPY_CFLOAT,
    NPY_DOUBLE, NPY_CDOUBLE, NPY_CDOUBLE,
    NPY_CDOUBLE, NPY_CDOUBLE, NPY_CDOUBLE
};

static char eigvals_types[] = {
    NPY_FLOAT, NPY_CFLOAT,
    NPY_DOUBLE, NPY_CDOUBLE,
    NPY_CDOUBLE, NPY_CDOUBLE
};

static char svd_1_1_types[] = {
    NPY_FLOAT, NPY_FLOAT,
    NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT, NPY_FLOAT,
    NPY_CDOUBLE, NPY_DOUBLE
};

static char svd_1_3_types[] = {
    NPY_FLOAT,   NPY_FLOAT,   NPY_FLOAT,  NPY_FLOAT,
    NPY_DOUBLE,  NPY_DOUBLE,  NPY_DOUBLE, NPY_DOUBLE,
    NPY_CFLOAT,  NPY_CFLOAT,  NPY_FLOAT,  NPY_CFLOAT,
    NPY_CDOUBLE, NPY_CDOUBLE, NPY_DOUBLE, NPY_CDOUBLE
};

/*  A,           b,           rcond,      x,           resid,      rank,    s,        */
static char lstsq_types[] = {
    NPY_FLOAT,   NPY_FLOAT,   NPY_FLOAT,  NPY_FLOAT,   NPY_FLOAT,  NPY_INT, NPY_FLOAT,
    NPY_DOUBLE,  NPY_DOUBLE,  NPY_DOUBLE, NPY_DOUBLE,  NPY_DOUBLE, NPY_INT, NPY_DOUBLE,
    NPY_CFLOAT,  NPY_CFLOAT,  NPY_FLOAT,  NPY_CFLOAT,  NPY_FLOAT,  NPY_INT, NPY_FLOAT,
    NPY_CDOUBLE, NPY_CDOUBLE, NPY_DOUBLE, NPY_CDOUBLE, NPY_DOUBLE, NPY_INT, NPY_DOUBLE,
};

typedef struct gufunc_descriptor_struct {
    char *name;
    char *signature;
    char *doc;
    int ntypes;
    int nin;
    int nout;
    PyUFuncGenericFunction *funcs;
    char *types;
} GUFUNC_DESCRIPTOR_t;

GUFUNC_DESCRIPTOR_t gufunc_descriptors [] = {
    {
        "slogdet",
        "(m,m)->(),()",
        "slogdet on the last two dimensions and broadcast on the rest. \n"\
        "Results in two arrays, one with sign and the other with log of the"\
        " determinants. \n"\
        "    \"(m,m)->(),()\" \n",
        4, 1, 2,
        FUNC_ARRAY_NAME(slogdet),
        slogdet_types
    },
    {
        "det",
        "(m,m)->()",
        "det of the last two dimensions and broadcast on the rest. \n"\
        "    \"(m,m)->()\" \n",
        4, 1, 1,
        FUNC_ARRAY_NAME(det),
        equal_2_types
    },
    {
        "eigh_lo",
        "(m,m)->(m),(m,m)",
        "eigh on the last two dimension and broadcast to the rest, using"\
        " lower triangle \n"\
        "Results in a vector of eigenvalues and a matrix with the"\
        "eigenvectors. \n"\
        "    \"(m,m)->(m),(m,m)\" \n",
        4, 1, 2,
        FUNC_ARRAY_NAME(eighlo),
        eigh_types
    },
    {
        "eigh_up",
        "(m,m)->(m),(m,m)",
        "eigh on the last two dimension and broadcast to the rest, using"\
        " upper triangle. \n"\
        "Results in a vector of eigenvalues and a matrix with the"\
        " eigenvectors. \n"\
        "    \"(m,m)->(m),(m,m)\" \n",
        4, 1, 2,
        FUNC_ARRAY_NAME(eighup),
        eigh_types
    },
    {
        "eigvalsh_lo",
        "(m,m)->(m)",
        "eigh on the last two dimension and broadcast to the rest, using"\
        " lower triangle. \n"\
        "Results in a vector of eigenvalues and a matrix with the"\
        "eigenvectors. \n"\
        "    \"(m,m)->(m)\" \n",
        4, 1, 1,
        FUNC_ARRAY_NAME(eigvalshlo),
        eighvals_types
    },
    {
        "eigvalsh_up",
        "(m,m)->(m)",
        "eigvalsh on the last two dimension and broadcast to the rest,"\
        " using upper triangle. \n"\
        "Results in a vector of eigenvalues and a matrix with the"\
        "eigenvectors.\n"\
        "    \"(m,m)->(m)\" \n",
        4, 1, 1,
        FUNC_ARRAY_NAME(eigvalshup),
        eighvals_types
    },
    {
        "solve",
        "(m,m),(m,n)->(m,n)",
        "solve the system a x = b, on the last two dimensions, broadcast"\
        " to the rest. \n"\
        "Results in a matrices with the solutions. \n"\
        "    \"(m,m),(m,n)->(m,n)\" \n",
        4, 2, 1,
        FUNC_ARRAY_NAME(solve),
        equal_3_types
    },
    {
        "solve1",
        "(m,m),(m)->(m)",
        "solve the system a x = b, for b being a vector, broadcast in"\
        " the outer dimensions. \n"\
        "Results in vectors with the solutions. \n"\
        "    \"(m,m),(m)->(m)\" \n",
        4, 2, 1,
        FUNC_ARRAY_NAME(solve1),
        equal_3_types
    },
    {
        "inv",
        "(m, m)->(m, m)",
        "compute the inverse of the last two dimensions and broadcast"\
        " to the rest. \n"\
        "Results in the inverse matrices. \n"\
        "    \"(m,m)->(m,m)\" \n",
        4, 1, 1,
        FUNC_ARRAY_NAME(inv),
        equal_2_types
    },
    {
        "cholesky_lo",
        "(m,m)->(m,m)",
        "cholesky decomposition of hermitian positive-definite matrices. \n"\
        "Broadcast to all outer dimensions. \n"\
        "    \"(m,m)->(m,m)\" \n",
        4, 1, 1,
        FUNC_ARRAY_NAME(cholesky_lo),
        equal_2_types
    },
    {
        "svd_m",
        "(m,n)->(m)",
        "svd when n>=m. ",
        4, 1, 1,
        FUNC_ARRAY_NAME(svd_N),
        svd_1_1_types
    },
    {
        "svd_n",
        "(m,n)->(n)",
        "svd when n<=m",
        4, 1, 1,
        FUNC_ARRAY_NAME(svd_N),
        svd_1_1_types
    },
    {
        "svd_m_s",
        "(m,n)->(m,m),(m),(m,n)",
        "svd when m<=n",
        4, 1, 3,
        FUNC_ARRAY_NAME(svd_S),
        svd_1_3_types
    },
    {
        "svd_n_s",
        "(m,n)->(m,n),(n),(n,n)",
        "svd when m>=n",
        4, 1, 3,
        FUNC_ARRAY_NAME(svd_S),
        svd_1_3_types
    },
    {
        "svd_m_f",
        "(m,n)->(m,m),(m),(n,n)",
        "svd when m<=n",
        4, 1, 3,
        FUNC_ARRAY_NAME(svd_A),
        svd_1_3_types
    },
    {
        "svd_n_f",
        "(m,n)->(m,m),(n),(n,n)",
        "svd when m>=n",
        4, 1, 3,
        FUNC_ARRAY_NAME(svd_A),
        svd_1_3_types
    },
    {
        "eig",
        "(m,m)->(m),(m,m)",
        "eig on the last two dimension and broadcast to the rest. \n"\
        "Results in a vector with the  eigenvalues and a matrix with the"\
        " eigenvectors. \n"\
        "    \"(m,m)->(m),(m,m)\" \n",
        3, 1, 2,
        FUNC_ARRAY_NAME(eig),
        eig_types
    },
    {
        "eigvals",
        "(m,m)->(m)",
        "eigvals on the last two dimension and broadcast to the rest. \n"\
        "Results in a vector of eigenvalues. \n",
        3, 1, 1,
        FUNC_ARRAY_NAME(eigvals),
        eigvals_types
    },
    {
        "lstsq_m",
        "(m,n),(m,nrhs),()->(n,nrhs),(nrhs),(),(m)",
        "least squares on the last two dimensions and broadcast to the rest. \n"\
        "For m <= n. \n",
        4, 3, 4,
        FUNC_ARRAY_NAME(lstsq),
        lstsq_types
    },
    {
        "lstsq_n",
        "(m,n),(m,nrhs),()->(n,nrhs),(nrhs),(),(n)",
        "least squares on the last two dimensions and broadcast to the rest. \n"\
        "For m >= n, meaning that residuals are produced. \n",
        4, 3, 4,
        FUNC_ARRAY_NAME(lstsq),
        lstsq_types
    }
};

static int
addUfuncs(PyObject *dictionary) {
    PyObject *f;
    int i;
    const int gufunc_count = sizeof(gufunc_descriptors)/
        sizeof(gufunc_descriptors[0]);
    for (i = 0; i < gufunc_count; i++) {
        GUFUNC_DESCRIPTOR_t* d = &gufunc_descriptors[i];
        f = PyUFunc_FromFuncAndDataAndSignature(d->funcs,
                                                array_of_nulls,
                                                d->types,
                                                d->ntypes,
                                                d->nin,
                                                d->nout,
                                                PyUFunc_None,
                                                d->name,
                                                d->doc,
                                                0,
                                                d->signature);
        if (f == NULL) {
            return -1;
        }
#if 0
        dump_ufunc_object((PyUFuncObject*) f);
#endif
        int ret = PyDict_SetItemString(dictionary, d->name, f);
        Py_DECREF(f);
        if (ret < 0) {
            return -1;
        }
    }
    return 0;
}



/* -------------------------------------------------------------------------- */
                  /* Module initialization stuff  */

static PyMethodDef UMath_LinAlgMethods[] = {
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        UMATH_LINALG_MODULE_NAME,
        NULL,
        -1,
        UMath_LinAlgMethods,
        NULL,
        NULL,
        NULL,
        NULL
};

PyObject *PyInit__umath_linalg(void)
{
    PyObject *m;
    PyObject *d;
    PyObject *version;

    init_constants();
    m = PyModule_Create(&moduledef);
    if (m == NULL) {
        return NULL;
    }

    import_array();
    import_ufunc();

    d = PyModule_GetDict(m);
    if (d == NULL) {
        return NULL;
    }

    version = PyUnicode_FromString(umath_linalg_version_string);
    if (version == NULL) {
        return NULL;
    }
    int ret = PyDict_SetItemString(d, "__version__", version);
    Py_DECREF(version);
    if (ret < 0) {
        return NULL;
    }

    /* Load the ufunc operators into the module's namespace */
    if (addUfuncs(d) < 0) {
        return NULL;
    }

    return m;
}

