#ifndef GSL_INTERFACE_H
#define GSL_INTERFACE_H

#include <gsl/gsl_odeiv2.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_matrix.h>

#ifdef __cplusplus
extern "C" {
#endif

/*****************************
 * Structure definitions
 *****************************
 */

/**
 * Encapsulation of the integrator;
*/
typedef struct cgsl_integrator{

  const gsl_odeiv2_step_type * step_type; /* time step allocation */
  gsl_odeiv2_step      * step;		  /* time step control */
  gsl_odeiv2_control   * control;	  /* definition of the system */
  gsl_odeiv2_system      system;	  /* definition of the driver */
  gsl_odeiv2_evolve    * evolution;	  /* emove forward in time*/
  gsl_odeiv2_driver    * driver;	  /* high level interface */

} cgsl_integrator;

//see <gsl/gsl_odeiv2.h> for an explanation of these
typedef int (* ode_function_ptr ) (double t, const double y[], double dydt[], void * params);
typedef int (* ode_jacobian_ptr ) (double t, const double y[], double * dfdy, double dfdt[], void * params);

/** Pre/post step callbacks
 *      t: Start of the current time step (communicationPoint)
 *     dt: Length of the current time step (communicationStepSize)
 *      n: Size of system
 *      y: For pre, the values of the variables before stepping. For post, after.
 * params: Opaque pointer
 */
typedef int (* pre_post_step_ptr ) (double t, double dt, int n, const double y[], void * params);

enum cgsl_callback_actions{
  CGSL_RESTART = 1                     // multistep integrators need a reset when
                                // used with the epce filter.
};

/**
 *
 *  This is intended to contain everything needed to integrate only one
 *  little bit at a time *and* to be able to backtrack when needed, and to
 *  have multiple instances of the model.
 *
 *  Here we use this for inheritance by aggregation so that each model is
 *  declared as
 *  typedef struct my_model{
 *     cgs_model   m;
 *         .
 *         .
 *     extra stuff
 *         .
 *         .
 *  }  my_model ;
*/

typedef struct cgsl_model{
  int n_variables;
  double *x;  	        /** state variables */
  double *x_backup;     /** for get/set FMU state */
  void * parameters;

  /** Definition of the dynamical system: this assumes an *explicit* ODE */
  ode_function_ptr function;
  /** Jacobian */
  ode_jacobian_ptr jacobian;

  /** Pre/post step functions */
  pre_post_step_ptr pre_step;
  pre_post_step_ptr post_step;

  /** Get/set FMU state
   * Used to copy/retreive internal state to/from temporary storage inside the model
   * params: Opaque pointer
   */
  void (* get_state) (struct cgsl_model *model);
  void (* set_state) (struct cgsl_model *model);

  /** Destructor */
  void (* free) (struct cgsl_model * model);

  /** Needed by ModelExchange to store content from parameters */
  void* (*get_model_parameters)(const struct cgsl_model *model);
} cgsl_model;

/**
 * Useful function for allocating the most common type of model
 */
cgsl_model* cgsl_model_default_alloc(
        int n_variables,            /** Number of variables */
        const double *x0,           /** Initial values. If NULL, initialize model->x to all zeroes instead */
        void *parameters,           /** User pointer */
        ode_function_ptr function,  /** ODE function */
        ode_jacobian_ptr jacobian,  /** Jacobian */
        pre_post_step_ptr pre_step, /** Pre-step function */
        pre_post_step_ptr post_step,/** Post-step function */
        size_t sz                   /** If sz > sizeof(cgsl_model) then allocate sz bytes instead.
                                        Useful for the my_model case described earlier in this file */
);

/** Default destructor. Frees model->x and the model itself */
void cgsl_model_default_free(cgsl_model *model);

/**
 * Finally we can put everything in a bag.  The spefic model only has to
 * fill in these fields.
 */
typedef struct cgsl_simulation {

  cgsl_model * model;         /** this contains the state variables */
  cgsl_integrator i;
  double t;		      /** current time */
  double t1;		      /** stop time */
  double h;		      /** first stepsize and current value */

  int   fixed_step;	      /** whether or not we move at fixed step */
  FILE * file;
  int store_data;	      /** whether or not we save the data in an array */
  double * data;              /** store variables as integration proceeds */
  int buffer_size;            /** size of data storage */
  int n;		      /** number of time steps taken */
  int save;		      /** persistence to file */
  int print;		      /** verbose on stderr */

  int iterations;             /** Number of iterations done by last call to cgsl_step_to() */
} cgsl_simulation;

/*****************************
 * Enum  definitions
 *****************************
 */

/**
 * List of available time integration methods in the gsl_odeiv2 library
 */
enum cgsl_integrator_ids
{
  rk2,		/* 0 */
  rk4,		/* 1 */
  rkf45,	/* 2 */
  rkck,		/* 3 */
  rk8pd,	/* 4 */
  rk1imp,	/* 5 */
  rk2imp,	/* 6 */
  rk4imp,	/* 7 */
  bsimp,	/* 8 */
  msadams,	/* 9 */
  msbdf		/*10 */
};

/* this will flag integrators which need restart */
  extern int restart_integrator;

/**************
 * Function declarations.
 **************
 */

/**
 * Essentially the constructor for the cgsl_simulation object.
 * The dynamical model is defined by a function, its Jacobian, an
 * opaque parameter struct, and a set of initial values.
 * All variables are assumed to be continuous.
 *
 *  \TODO: fix semantics for saving.  Use a filename
 *   instead of descriptor?  Open and close file automatically?
 */
cgsl_simulation cgsl_init_simulation_tolerance(
  cgsl_model * model, /** the model we work on */
        enum cgsl_integrator_ids integrator, /** Integrator ID   */
        double h,                    /** Initial time-step: must be non-zero, even  with variable step*/
        int fixed_step,		     /** Boolean */
        int save,		     /** Boolean */
        int print,		     /** Boolean  */
  FILE *f,		             /** File descriptor if 'save' is enabled  */
  double reltol, double abstol
  );

cgsl_simulation cgsl_init_simulation(
  cgsl_model * model, /** the model we work on */
        enum cgsl_integrator_ids integrator, /** Integrator ID   */
        double h,                    /** Initial time-step: must be non-zero, even  with variable step*/
        int fixed_step,		     /** Boolean */
        int save,		     /** Boolean */
        int print,		     /** Boolean  */
        FILE *f		             /** File descriptor if 'save' is enabled  */
  );

/**
 *  Memory deallocation.
 */
void  cgsl_free_simulation( cgsl_simulation sim );

/**  Step from current time to next communication point. */
int cgsl_step_to(void * _s,  double comm_point, double comm_step ) ;

/** Accessor */
const gsl_odeiv2_step_type * cgsl_get_integrator( int  i ) ;

/**  Commit to file. */
void cgsl_save_data( struct cgsl_simulation * sim );

/** Mutators for fixed step*/
/** \TODO: make this a toggle */
void cgsl_simulation_set_fixed_step( cgsl_simulation * s, double h );
void cgsl_simulation_set_variable_step( cgsl_simulation * s );

/** Get/set FMU state */
void cgsl_simulation_get( cgsl_simulation *s );
void cgsl_simulation_set( cgsl_simulation *s );

#ifdef __cplusplus
}
#endif

#endif


/* ******************************************************************************
 *
 *
 * This program and the accompanying materials are made available under the
 * terms of the Apache License, Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0.
 *
 *  See the NOTICE file distributed with this work for additional
 *  information regarding copyright ownership.
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 ******************************************************************************/

//
// Created by agibsonccc on 1/26/16.
//

#ifndef NATIVEOPERATIONS_CBLAS_H
#define NATIVEOPERATIONS_CBLAS_H

#ifndef __STANDALONE_BUILD__
#include "config.h"
#endif

#ifdef __MKL_CBLAS_H__
// CBLAS from MKL is already included
#define CBLAS_H
#endif

#ifdef HAVE_OPENBLAS
// include CBLAS from OpenBLAS
#ifdef __GNUC__
#include_next <cblas.h>
#else
#include <cblas.h>
#endif
#define CBLAS_H
#endif

#ifndef CBLAS_H

#ifdef __cplusplus
extern "C" {
#endif
#ifndef CBLAS_ENUM_DEFINED_H
#define CBLAS_ENUM_DEFINED_H
enum CBLAS_ORDER { CblasRowMajor = 101, CblasColMajor = 102 };
enum CBLAS_TRANSPOSE { CblasNoTrans = 111, CblasTrans = 112, CblasConjTrans = 113, AtlasConj = 114 };
enum CBLAS_UPLO { CblasUpper = 121, CblasLower = 122 };
enum CBLAS_DIAG { CblasNonUnit = 131, CblasUnit = 132 };
enum CBLAS_SIDE { CblasLeft = 141, CblasRight = 142 };
#endif

#ifndef CBLAS_ENUM_ONLY
#define CBLAS_H
#define CBLAS_INDEX int

int cblas_errprn(int ierr, int info, char *form, ...);
void cblas_xerbla(int p, char *rout, char *form, ...);

#ifdef __MKL
void MKL_Set_Num_Threads(int num);
int MKL_Domain_Set_Num_Threads(int num, int domain);
int MKL_Set_Num_Threads_Local(int num);
#elif __OPENBLAS
void openblas_set_num_threads(int num);
#else
// do nothing
#endif

/*
 * ===========================================================================
 * Prototypes for level 1 BLAS functions (complex are recast as routines)
 * ===========================================================================
 */
float cblas_sdsdot(int N, float alpha, float *X, int incX, float *Y, int incY);
double cblas_dsdot(int N, float *X, int incX, float *Y, int incY);
float cblas_sdot(int N, float *X, int incX, float *Y, int incY);
double cblas_ddot(int N, double *X, int incX, double *Y, int incY);
/*
 * Functions having prefixes Z and C only
 */
void cblas_cdotu_sub(int N, void *X, int incX, void *Y, int incY, void *dotu);
void cblas_cdotc_sub(int N, void *X, int incX, void *Y, int incY, void *dotc);

void cblas_zdotu_sub(int N, void *X, int incX, void *Y, int incY, void *dotu);
void cblas_zdotc_sub(int N, void *X, int incX, void *Y, int incY, void *dotc);

/*
 * Functions having prefixes S D SC DZ
 */
float cblas_snrm2(int N, float *X, int incX);
float cblas_sasum(int N, float *X, int incX);

double cblas_dnrm2(int N, double *X, int incX);
double cblas_dasum(int N, double *X, int incX);

float cblas_scnrm2(int N, void *X, int incX);
float cblas_scasum(int N, void *X, int incX);

double cblas_dznrm2(int N, void *X, int incX);
double cblas_dzasum(int N, void *X, int incX);

/*
 * Functions having standard 4 prefixes (S D C Z)
 */
CBLAS_INDEX cblas_isamax(int N, float *X, int incX);
CBLAS_INDEX cblas_idamax(int N, double *X, int incX);
CBLAS_INDEX cblas_icamax(int N, void *X, int incX);
CBLAS_INDEX cblas_izamax(int N, void *X, int incX);

/*
 * ===========================================================================
 * Prototypes for level 1 BLAS routines
 * ===========================================================================
 */

/*
 * Routines with standard 4 prefixes (s, d, c, z)
 */
void cblas_sswap(int N, float *X, int incX, float *Y, int incY);
void cblas_scopy(int N, float *X, int incX, float *Y, int incY);
void cblas_saxpy(int N, float alpha, float *X, int incX, float *Y, int incY);
void catlas_saxpby(int N, float alpha, float *X, int incX, float beta, float *Y, int incY);
void catlas_sset(int N, float alpha, float *X, int incX);

void cblas_dswap(int N, double *X, int incX, double *Y, int incY);
void cblas_dcopy(int N, double *X, int incX, double *Y, int incY);
void cblas_daxpy(int N, double alpha, double *X, int incX, double *Y, int incY);
void catlas_daxpby(int N, double alpha, double *X, int incX, double beta, double *Y, int incY);
void catlas_dset(int N, double alpha, double *X, int incX);

void cblas_cswap(int N, void *X, int incX, void *Y, int incY);
void cblas_ccopy(int N, void *X, int incX, void *Y, int incY);
void cblas_caxpy(int N, void *alpha, void *X, int incX, void *Y, int incY);
void catlas_caxpby(int N, void *alpha, void *X, int incX, void *beta, void *Y, int incY);
void catlas_cset(int N, void *alpha, void *X, int incX);

void cblas_zswap(int N, void *X, int incX, void *Y, int incY);
void cblas_zcopy(int N, void *X, int incX, void *Y, int incY);
void cblas_zaxpy(int N, void *alpha, void *X, int incX, void *Y, int incY);
void catlas_zaxpby(int N, void *alpha, void *X, int incX, void *beta, void *Y, int incY);
void catlas_zset(int N, void *alpha, void *X, int incX);

/*
 * Routines with S and D prefix only
 */
void cblas_srotg(float *a, float *b, float *c, float *s);
void cblas_srotmg(float *d1, float *d2, float *b1, float b2, float *P);
void cblas_srot(int N, float *X, int incX, float *Y, int incY, float c, float s);
void cblas_srotm(int N, float *X, int incX, float *Y, int incY, float *P);

void cblas_drotg(double *a, double *b, double *c, double *s);
void cblas_drotmg(double *d1, double *d2, double *b1, double b2, double *P);
void cblas_drot(int N, double *X, int incX, double *Y, int incY, double c, double s);
void cblas_drotm(int N, double *X, int incX, double *Y, int incY, double *P);

/*
 * Routines with S D C Z CS and ZD prefixes
 */
void cblas_sscal(int N, float alpha, float *X, int incX);
void cblas_dscal(int N, double alpha, double *X, int incX);
void cblas_cscal(int N, void *alpha, void *X, int incX);
void cblas_zscal(int N, void *alpha, void *X, int incX);
void cblas_csscal(int N, float alpha, void *X, int incX);
void cblas_zdscal(int N, double alpha, void *X, int incX);

/*
 * Extra reference routines provided by ATLAS, but not mandated by the standard
 */
void cblas_crotg(void *a, void *b, void *c, void *s);
void cblas_zrotg(void *a, void *b, void *c, void *s);
void cblas_csrot(int N, void *X, int incX, void *Y, int incY, float c, float s);
void cblas_zdrot(int N, void *X, int incX, void *Y, int incY, double c, double s);

/*
 * ===========================================================================
 * Prototypes for level 2 BLAS
 * ===========================================================================
 */

/*
 * Routines with standard 4 prefixes (S, D, C, Z)
 */
void cblas_sgemv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, float alpha, float *A, int lda,
                 float *X, int incX, float beta, float *Y, int incY);
void cblas_sgbmv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, int KL, int KU, float alpha,
                 float *A, int lda, float *X, int incX, float beta, float *Y, int incY);
void cblas_strmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 float *A, int lda, float *X, int incX);
void cblas_stbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, float *A, int lda, float *X, int incX);
void cblas_stpmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 float *Ap, float *X, int incX);
void cblas_strsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 float *A, int lda, float *X, int incX);
void cblas_stbsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, float *A, int lda, float *X, int incX);
void cblas_stpsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 float *Ap, float *X, int incX);

void cblas_dgemv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, double alpha, double *A, int lda,
                 double *X, int incX, double beta, double *Y, int incY);
void cblas_dgbmv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, int KL, int KU, double alpha,
                 double *A, int lda, double *X, int incX, double beta, double *Y, int incY);
void cblas_dtrmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 double *A, int lda, double *X, int incX);
void cblas_dtbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, double *A, int lda, double *X, int incX);
void cblas_dtpmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 double *Ap, double *X, int incX);
void cblas_dtrsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 double *A, int lda, double *X, int incX);
void cblas_dtbsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, double *A, int lda, double *X, int incX);
void cblas_dtpsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 double *Ap, double *X, int incX);

void cblas_cgemv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, void *alpha, void *A, int lda,
                 void *X, int incX, void *beta, void *Y, int incY);
void cblas_cgbmv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, int KL, int KU, void *alpha,
                 void *A, int lda, void *X, int incX, void *beta, void *Y, int incY);
void cblas_ctrmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *A, int lda, void *X, int incX);
void cblas_ctbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, void *A, int lda, void *X, int incX);
void cblas_ctpmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *Ap, void *X, int incX);
void cblas_ctrsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *A, int lda, void *X, int incX);
void cblas_ctbsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, void *A, int lda, void *X, int incX);
void cblas_ctpsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *Ap, void *X, int incX);

void cblas_zgemv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, void *alpha, void *A, int lda,
                 void *X, int incX, void *beta, void *Y, int incY);
void cblas_zgbmv(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, int M, int N, int KL, int KU, void *alpha,
                 void *A, int lda, void *X, int incX, void *beta, void *Y, int incY);
void cblas_ztrmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *A, int lda, void *X, int incX);
void cblas_ztbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, void *A, int lda, void *X, int incX);
void cblas_ztpmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *Ap, void *X, int incX);
void cblas_ztrsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *A, int lda, void *X, int incX);
void cblas_ztbsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 int K, void *A, int lda, void *X, int incX);
void cblas_ztpsv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA, enum CBLAS_DIAG Diag, int N,
                 void *Ap, void *X, int incX);

/*
 * Routines with S and D prefixes only
 */
void cblas_ssymv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, float *A, int lda, float *X,
                 int incX, float beta, float *Y, int incY);
void cblas_ssbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, int K, float alpha, float *A, int lda, float *X,
                 int incX, float beta, float *Y, int incY);
void cblas_sspmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, float *Ap, float *X, int incX,
                 float beta, float *Y, int incY);
void cblas_sger(enum CBLAS_ORDER Order, int M, int N, float alpha, float *X, int incX, float *Y, int incY, float *A,
                int lda);
void cblas_ssyr(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, float *X, int incX, float *A,
                int lda);
void cblas_sspr(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, float *X, int incX, float *Ap);
void cblas_ssyr2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, float *X, int incX, float *Y,
                 int incY, float *A, int lda);
void cblas_sspr2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, float *X, int incX, float *Y,
                 int incY, float *A);

void cblas_dsymv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, double *A, int lda, double *X,
                 int incX, double beta, double *Y, int incY);
void cblas_dsbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, int K, double alpha, double *A, int lda,
                 double *X, int incX, double beta, double *Y, int incY);
void cblas_dspmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, double *Ap, double *X, int incX,
                 double beta, double *Y, int incY);
void cblas_dger(enum CBLAS_ORDER Order, int M, int N, double alpha, double *X, int incX, double *Y, int incY, double *A,
                int lda);
void cblas_dsyr(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, double *X, int incX, double *A,
                int lda);
void cblas_dspr(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, double *X, int incX, double *Ap);
void cblas_dsyr2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, double *X, int incX, double *Y,
                 int incY, double *A, int lda);
void cblas_dspr2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, double *X, int incX, double *Y,
                 int incY, double *A);

/*
 * Routines with C and Z prefixes only
 */
void cblas_chemv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *A, int lda, void *X, int incX,
                 void *beta, void *Y, int incY);
void cblas_chbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, int K, void *alpha, void *A, int lda, void *X,
                 int incX, void *beta, void *Y, int incY);
void cblas_chpmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *Ap, void *X, int incX,
                 void *beta, void *Y, int incY);
void cblas_cgeru(enum CBLAS_ORDER Order, int M, int N, void *alpha, void *X, int incX, void *Y, int incY, void *A,
                 int lda);
void cblas_cgerc(enum CBLAS_ORDER Order, int M, int N, void *alpha, void *X, int incX, void *Y, int incY, void *A,
                 int lda);
void cblas_cher(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, void *X, int incX, void *A, int lda);
void cblas_chpr(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, float alpha, void *X, int incX, void *A);
void cblas_cher2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *X, int incX, void *Y, int incY,
                 void *A, int lda);
void cblas_chpr2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *X, int incX, void *Y, int incY,
                 void *Ap);

void cblas_zhemv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *A, int lda, void *X, int incX,
                 void *beta, void *Y, int incY);
void cblas_zhbmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, int K, void *alpha, void *A, int lda, void *X,
                 int incX, void *beta, void *Y, int incY);
void cblas_zhpmv(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *Ap, void *X, int incX,
                 void *beta, void *Y, int incY);
void cblas_zgeru(enum CBLAS_ORDER Order, int M, int N, void *alpha, void *X, int incX, void *Y, int incY, void *A,
                 int lda);
void cblas_zgerc(enum CBLAS_ORDER Order, int M, int N, void *alpha, void *X, int incX, void *Y, int incY, void *A,
                 int lda);
void cblas_zher(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, void *X, int incX, void *A, int lda);
void cblas_zhpr(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, double alpha, void *X, int incX, void *A);
void cblas_zher2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *X, int incX, void *Y, int incY,
                 void *A, int lda);
void cblas_zhpr2(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, int N, void *alpha, void *X, int incX, void *Y, int incY,
                 void *Ap);

/*
 * ===========================================================================
 * Prototypes for level 3 BLAS
 * ===========================================================================
 */

/*
 * Routines with standard 4 prefixes (S, D, C, Z)
 */
void cblas_sgemm(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, enum CBLAS_TRANSPOSE TransB, int M, int N, int K,
                 float alpha, float *A, int lda, float *B, int ldb, float beta, float *C, int ldc);
void cblas_ssymm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, int M, int N, float alpha,
                 float *A, int lda, float *B, int ldb, float beta, float *C, int ldc);
void cblas_ssyrk(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, float alpha,
                 float *A, int lda, float beta, float *C, int ldc);
void cblas_ssyr2k(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, float alpha,
                  float *A, int lda, float *B, int ldb, float beta, float *C, int ldc);
void cblas_strmm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, float alpha, float *A, int lda, float *B, int ldb);
void cblas_strsm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, float alpha, float *A, int lda, float *B, int ldb);

void cblas_dgemm(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, enum CBLAS_TRANSPOSE TransB, int M, int N, int K,
                 double alpha, double *A, int lda, double *B, int ldb, double beta, double *C, int ldc);
void cblas_dsymm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, int M, int N, double alpha,
                 double *A, int lda, double *B, int ldb, double beta, double *C, int ldc);
void cblas_dsyrk(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, double alpha,
                 double *A, int lda, double beta, double *C, int ldc);
void cblas_dsyr2k(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, double alpha,
                  double *A, int lda, double *B, int ldb, double beta, double *C, int ldc);
void cblas_dtrmm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, double alpha, double *A, int lda, double *B, int ldb);
void cblas_dtrsm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, double alpha, double *A, int lda, double *B, int ldb);

void cblas_cgemm(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, enum CBLAS_TRANSPOSE TransB, int M, int N, int K,
                 void *alpha, void *A, int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_csymm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, int M, int N, void *alpha, void *A,
                 int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_csyrk(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, void *alpha,
                 void *A, int lda, void *beta, void *C, int ldc);
void cblas_csyr2k(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, void *alpha,
                  void *A, int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_ctrmm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, void *alpha, void *A, int lda, void *B, int ldb);
void cblas_ctrsm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, void *alpha, void *A, int lda, void *B, int ldb);

void cblas_zgemm(enum CBLAS_ORDER Order, enum CBLAS_TRANSPOSE TransA, enum CBLAS_TRANSPOSE TransB, int M, int N, int K,
                 void *alpha, void *A, int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_zsymm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, int M, int N, void *alpha, void *A,
                 int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_zsyrk(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, void *alpha,
                 void *A, int lda, void *beta, void *C, int ldc);
void cblas_zsyr2k(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, void *alpha,
                  void *A, int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_ztrmm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, void *alpha, void *A, int lda, void *B, int ldb);
void cblas_ztrsm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE TransA,
                 enum CBLAS_DIAG Diag, int M, int N, void *alpha, void *A, int lda, void *B, int ldb);

/*
 * Routines with prefixes C and Z only
 */
void cblas_chemm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, int M, int N, void *alpha, void *A,
                 int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_cherk(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, float alpha,
                 void *A, int lda, float beta, void *C, int ldc);
void cblas_cher2k(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, void *alpha,
                  void *A, int lda, void *B, int ldb, float beta, void *C, int ldc);
void cblas_zhemm(enum CBLAS_ORDER Order, enum CBLAS_SIDE Side, enum CBLAS_UPLO Uplo, int M, int N, void *alpha, void *A,
                 int lda, void *B, int ldb, void *beta, void *C, int ldc);
void cblas_zherk(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, double alpha,
                 void *A, int lda, double beta, void *C, int ldc);
void cblas_zher2k(enum CBLAS_ORDER Order, enum CBLAS_UPLO Uplo, enum CBLAS_TRANSPOSE Trans, int N, int K, void *alpha,
                  void *A, int lda, void *B, int ldb, double beta, void *C, int ldc);

int cblas_errprn(int ierr, int info, char *form, ...);
#ifdef __cplusplus
}
#endif
#endif /* end #ifdef CBLAS_ENUM_ONLY */
#endif
#endif  // NATIVEOPERATIONS_CBLAS_H


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_linalg.h>

#include "norm.h"
#include "constants.h"

int readFileVector( gsl_vector *vector, char *filePath, char *fileName )                //read file vals into vector
{
	char tmpFilepath[120] = "\0";
	char tmpReading[20];
	char *tmp2;
	double reading;                                 //WARNING this is similar to global var $readings, consider renaming

	strcat( tmpFilepath, filePath );                //TODO replace this and the first instance of strcat into strcpy, that should work
	strcat( tmpFilepath, fileName );        //combines the filepath and name into one searchable /path/file
	FILE *file = fopen( tmpFilepath, "rt");
	printf("%s\n", tmpFilepath);                    //TODO DELETE THIS
	if ( file == NULL ) {                   //check if file exists
		fprintf( stderr, "Error, unable to locate the Mu data file\n");
		exit(100);
	}

	for ( int i = 0; i < skip; i++)         //move past header of read files
	{
		fgets( tmpReading, 19, file );
	}

	for ( int i = 0; i < readings; i++ )
	{
		fgets( tmpReading, 19, file );

		tmp2 = strrchr( tmpReading, '\t' );
		tmp2 = tmp2 + 1;
		reading = atof(tmp2);
		gsl_vector_set( vector, i, reading);
	}
	fclose(file);
	return 0;
}

int readFileMatrix( gsl_matrix *matrix, char *filePath, int argc, char *argv[] )
{
	char *tmp2;
	double reading;
	for ( int j = 2; j < argc; j++ )
	{
		gsl_vector_view column;
		char tmpFilepath[120] = "\0";                           //TODO replace this and the first instance of strcat into strcpy, that should work
		char tmpReading[20];
		strcat( tmpFilepath, filePath );
		strcat( tmpFilepath, argv[j] );         //combines the filepath and name into one searchable /path/file
		FILE *file = fopen( tmpFilepath, "rt");
		printf("%s\n", tmpFilepath);                            //TODO DELETE THIS
		if ( file == NULL ) {                   //check if file exists
			fprintf( stderr, "Error, unable to locate a reference data file\n");
			exit(100);
		}
		for ( int i = 0; i < skip; i++)         //move past header of read files
		{
			fgets( tmpReading, 20, file);
		}
		for ( int i = 0; i < readings; i++ ) //fill the ith element of the jth column
		{
			fgets( tmpReading, 19, file );

			tmp2 = strrchr( tmpReading, '\t' );
			tmp2 = tmp2 + 1;
			reading = atof(tmp2);
			gsl_matrix_set( matrix, i, j-2, reading );
		}
		fclose(file);
		column = gsl_matrix_column( matrix, j-2 );                              //NORMALISE EACH REFERENCE VECOTR.
		normalizeVector( &column.vector );
	}

	return 0;
}


/*
#
# * The source code in this file is developed independently by NEC Corporation.
#
# # NLCPy License #
#
#     Copyright (c) 2020-2021 NEC Corporation
#     All rights reserved.
#
#     Redistribution and use in source and binary forms, with or without
#     modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither NEC Corporation nor the names of its contributors may be
#       used to endorse or promote products derived from this software
#       without specific prior written permission.
#
#     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
#     FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
*/

#include <cblas.h>
#include "nlcpy.h"

uint64_t wrapper_cblas_sdot(ve_arguments *args, int32_t *psw)
{
#ifdef _OPENMP
#pragma omp single
#endif /* _OPENMP */
{
    ve_array *x = &(args->binary.x);
    ve_array *y = &(args->binary.y);
    ve_array *z = &(args->binary.z);

    float *px = (float *)x->ve_adr;
    if (px == NULL) {
        px = (float *)nlcpy__get_scalar(x);
        if (px == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    float *py = (float *)y->ve_adr;
    if (py == NULL) {
        py = (float *)nlcpy__get_scalar(y);
        if (py == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    float *pz = (float *)z->ve_adr;
    if (pz == NULL) {
       return (uint64_t)NLCPY_ERROR_MEMORY;
    }
    assert(x->ndim <= 1);
    assert(y->ndim <= 1);
    assert(z->ndim <= 1);
    assert(x->size == y->size);

    *pz = cblas_sdot(x->size, px, x->strides[0] / x->itemsize,
                            py, y->strides[0] / y->itemsize);
} /* omp single */
    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_ddot(ve_arguments *args, int32_t *psw)
{
#ifdef _OPENMP
#pragma omp single
#endif /* _OPENMP */
{
    ve_array *x = &(args->binary.x);
    ve_array *y = &(args->binary.y);
    ve_array *z = &(args->binary.z);

    double *px = (double *)x->ve_adr;
    if (px == NULL) {
        px = (double *)nlcpy__get_scalar(x);
        if (px == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    double *py = (double *)y->ve_adr;
    if (py == NULL) {
        py = (double *)nlcpy__get_scalar(y);
        if (py == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    double *pz = (double *)z->ve_adr;
    if (pz == NULL) {
       return (uint64_t)NLCPY_ERROR_MEMORY;
    }
    assert(x->ndim <= 1);
    assert(y->ndim <= 1);
    assert(z->ndim <= 1);
    assert(x->size == y->size);

    *pz = cblas_ddot(x->size, px, x->strides[0] / x->itemsize,
                            py, y->strides[0] / y->itemsize);
} /* omp single */
    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_cdotu_sub(ve_arguments *args, int32_t *psw)
{
#ifdef _OPENMP
#pragma omp single
#endif /* _OPENMP */
{
    ve_array *x = &(args->binary.x);
    ve_array *y = &(args->binary.y);
    ve_array *z = &(args->binary.z);

    float _Complex *px = (float _Complex *)x->ve_adr;
    if (px == NULL) {
        px = (float _Complex *)nlcpy__get_scalar(x);
        if (px == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    float _Complex *py = (float _Complex *)y->ve_adr;
    if (py == NULL) {
        py = (float _Complex *)nlcpy__get_scalar(y);
        if (py == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    float _Complex *pz = (float _Complex *)z->ve_adr;
    if (pz == NULL) {
       return (uint64_t)NLCPY_ERROR_MEMORY;
    }
    assert(x->ndim <= 1);
    assert(y->ndim <= 1);
    assert(z->ndim <= 1);
    assert(x->size == y->size);

    cblas_cdotu_sub(x->size, px, x->strides[0] / x->itemsize,
                        py, y->strides[0] / y->itemsize, pz);
} /* omp single */
    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_zdotu_sub(ve_arguments *args, int32_t *psw)
{
#ifdef _OPENMP
#pragma omp single
#endif /* _OPENMP */
{
    ve_array *x = &(args->binary.x);
    ve_array *y = &(args->binary.y);
    ve_array *z = &(args->binary.z);

    double _Complex *px = (double _Complex *)x->ve_adr;
    if (px == NULL) {
        px = (double _Complex *)nlcpy__get_scalar(x);
        if (px == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    double _Complex *py = (double _Complex *)y->ve_adr;
    if (py == NULL) {
        py = (double _Complex *)nlcpy__get_scalar(y);
        if (py == NULL) {
            return NLCPY_ERROR_MEMORY;
        }
    }
    double _Complex *pz = (double _Complex *)z->ve_adr;
    if (pz == NULL) {
       return (uint64_t)NLCPY_ERROR_MEMORY;
    }
    assert(x->ndim <= 1);
    assert(y->ndim <= 1);
    assert(z->ndim <= 1);
    assert(x->size == y->size);

    cblas_zdotu_sub(x->size, px, x->strides[0] / x->itemsize,
                        py, y->strides[0] / y->itemsize, pz);
} /* omp single */
    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_sgemm(ve_arguments *args, int32_t *psw)
{
    const int32_t order = args->gemm.order;
    const int32_t transA = args->gemm.transA;
    const int32_t transB = args->gemm.transB;
    const int32_t m = args->gemm.m;
    const int32_t n = args->gemm.n;
    const int32_t k = args->gemm.k;
    const float alpha = *((float *)nlcpy__get_scalar(&(args->gemm.alpha)));
    float* const a = (float *)args->gemm.a.ve_adr;
    const int32_t lda = args->gemm.lda;
    float* const b = (float *)args->gemm.b.ve_adr;
    const int32_t ldb = args->gemm.ldb;
    const float beta = *((float *)nlcpy__get_scalar(&(args->gemm.beta)));
    float* const c = (float *)args->gemm.c.ve_adr;
    const int32_t ldc = args->gemm.ldc;

    if (a == NULL || b == NULL || c == NULL) {
        return NLCPY_ERROR_MEMORY;
    }

#ifdef _OPENMP
    const int32_t nt = omp_get_num_threads();
    const int32_t it = omp_get_thread_num();
#else
    const int32_t nt = 1;
    const int32_t it = 0;
#endif /* _OPENMP */

    const int32_t m_s = m * it / nt;
    const int32_t m_e = m * (it + 1) / nt;
    const int32_t m_d = m_e - m_s;
    const int32_t n_s = n * it / nt;
    const int32_t n_e = n * (it + 1) / nt;
    const int32_t n_d = n_e - n_s;

    int32_t mode = 1;
    if ( n > nt ) {
        mode = 2;
    }
    int32_t iar, iac, ibr, ibc, icr, icc;
    if (transA == CblasNoTrans ) {
        iar = 1;
        iac = lda;
    } else {
        iar = lda;
        iac = 1;
    }
    if (transB == CblasNoTrans ) {
        ibr = 1;
        ibc = ldb;
    } else {
        ibr = ldb;
        ibc = 1;
    }
    if (order == CblasColMajor ) {
        icr = 1;
        icc = ldc;
    } else {
        icr = ldc;
        icc = 1;
    }

    if (order == CblasColMajor) {
        if ( mode == 1 ) {
            // split 'm'
            cblas_sgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iar, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_sgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibc, ldb, beta, c + n_s * icc, ldc);
        }
    } else {
        if ( mode == 1 ) {
            // split 'm'
            cblas_sgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iac, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_sgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibr, ldb, beta, c + n_s * icc, ldc);
        }
    }

    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_dgemm(ve_arguments *args, int32_t *psw)
{
    const int32_t order = args->gemm.order;
    const int32_t transA = args->gemm.transA;
    const int32_t transB = args->gemm.transB;
    const int32_t m = args->gemm.m;
    const int32_t n = args->gemm.n;
    const int32_t k = args->gemm.k;
    const double alpha = *((double *)nlcpy__get_scalar(&(args->gemm.alpha)));
    double* const a = (double *)args->gemm.a.ve_adr;
    const int32_t lda = args->gemm.lda;
    double* const b = (double *)args->gemm.b.ve_adr;
    const int32_t ldb = args->gemm.ldb;
    const double beta = *((double *)nlcpy__get_scalar(&(args->gemm.beta)));
    double* const c = (double *)args->gemm.c.ve_adr;
    const int32_t ldc = args->gemm.ldc;

    if (a == NULL || b == NULL || c == NULL) {
        return NLCPY_ERROR_MEMORY;
    }

#ifdef _OPENMP
    const int32_t nt = omp_get_num_threads();
    const int32_t it = omp_get_thread_num();
#else
    const int32_t nt = 1;
    const int32_t it = 0;
#endif /* _OPENMP */

    const int32_t m_s = m * it / nt;
    const int32_t m_e = m * (it + 1) / nt;
    const int32_t m_d = m_e - m_s;
    const int32_t n_s = n * it / nt;
    const int32_t n_e = n * (it + 1) / nt;
    const int32_t n_d = n_e - n_s;

    int32_t mode = 1;
    if ( n > nt ) {
        mode = 2;
    }
    int32_t iar, iac, ibr, ibc, icr, icc;
    if (transA == CblasNoTrans ) {
        iar = 1;
        iac = lda;
    } else {
        iar = lda;
        iac = 1;
    }
    if (transB == CblasNoTrans ) {
        ibr = 1;
        ibc = ldb;
    } else {
        ibr = ldb;
        ibc = 1;
    }
    if (order == CblasColMajor ) {
        icr = 1;
        icc = ldc;
    } else {
        icr = ldc;
        icc = 1;
    }

    if (order == CblasColMajor) {
        if ( mode == 1 ) {
            // split 'm'
            cblas_dgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iar, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_dgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibc, ldb, beta, c + n_s * icc, ldc);
        }
    } else {
        if ( mode == 1 ) {
            // split 'm'
            cblas_dgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iac, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_dgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibr, ldb, beta, c + n_s * icc, ldc);
        }
    }

    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_cgemm(ve_arguments *args, int32_t *psw)
{
    const int32_t order = args->gemm.order;
    const int32_t transA = args->gemm.transA;
    const int32_t transB = args->gemm.transB;
    const int32_t m = args->gemm.m;
    const int32_t n = args->gemm.n;
    const int32_t k = args->gemm.k;
    const void *alpha = (void *)nlcpy__get_scalar(&(args->gemm.alpha));
    if (alpha == NULL) return (uint64_t)NLCPY_ERROR_MEMORY;
    float  _Complex* const a = (float  _Complex *)args->gemm.a.ve_adr;
    const int32_t lda = args->gemm.lda;
    float  _Complex* const b = (float  _Complex *)args->gemm.b.ve_adr;
    const int32_t ldb = args->gemm.ldb;
    const void *beta = (void *)nlcpy__get_scalar(&(args->gemm.beta));
    if (beta == NULL) return (uint64_t)NLCPY_ERROR_MEMORY;
    float  _Complex* const c = (float  _Complex *)args->gemm.c.ve_adr;
    const int32_t ldc = args->gemm.ldc;

    if (a == NULL || b == NULL || c == NULL) {
        return NLCPY_ERROR_MEMORY;
    }

#ifdef _OPENMP
    const int32_t nt = omp_get_num_threads();
    const int32_t it = omp_get_thread_num();
#else
    const int32_t nt = 1;
    const int32_t it = 0;
#endif /* _OPENMP */

    const int32_t m_s = m * it / nt;
    const int32_t m_e = m * (it + 1) / nt;
    const int32_t m_d = m_e - m_s;
    const int32_t n_s = n * it / nt;
    const int32_t n_e = n * (it + 1) / nt;
    const int32_t n_d = n_e - n_s;

    int32_t mode = 1;
    if ( n > nt ) {
        mode = 2;
    }
    int32_t iar, iac, ibr, ibc, icr, icc;
    if (transA == CblasNoTrans ) {
        iar = 1;
        iac = lda;
    } else {
        iar = lda;
        iac = 1;
    }
    if (transB == CblasNoTrans ) {
        ibr = 1;
        ibc = ldb;
    } else {
        ibr = ldb;
        ibc = 1;
    }
    if (order == CblasColMajor ) {
        icr = 1;
        icc = ldc;
    } else {
        icr = ldc;
        icc = 1;
    }

    if (order == CblasColMajor) {
        if ( mode == 1 ) {
            // split 'm'
            cblas_cgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iar, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_cgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibc, ldb, beta, c + n_s * icc, ldc);
        }
    } else {
        if ( mode == 1 ) {
            // split 'm'
            cblas_cgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iac, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_cgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibr, ldb, beta, c + n_s * icc, ldc);
        }
    }

    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}

uint64_t wrapper_cblas_zgemm(ve_arguments *args, int32_t *psw)
{
    const int32_t order = args->gemm.order;
    const int32_t transA = args->gemm.transA;
    const int32_t transB = args->gemm.transB;
    const int32_t m = args->gemm.m;
    const int32_t n = args->gemm.n;
    const int32_t k = args->gemm.k;
    const void *alpha = (void *)nlcpy__get_scalar(&(args->gemm.alpha));
    if (alpha == NULL) return (uint64_t)NLCPY_ERROR_MEMORY;
    double _Complex* const a = (double _Complex *)args->gemm.a.ve_adr;
    const int32_t lda = args->gemm.lda;
    double _Complex* const b = (double _Complex *)args->gemm.b.ve_adr;
    const int32_t ldb = args->gemm.ldb;
    const void *beta = (void *)nlcpy__get_scalar(&(args->gemm.beta));
    if (beta == NULL) return (uint64_t)NLCPY_ERROR_MEMORY;
    double _Complex* const c = (double _Complex *)args->gemm.c.ve_adr;
    const int32_t ldc = args->gemm.ldc;

    if (a == NULL || b == NULL || c == NULL) {
        return NLCPY_ERROR_MEMORY;
    }

#ifdef _OPENMP
    const int32_t nt = omp_get_num_threads();
    const int32_t it = omp_get_thread_num();
#else
    const int32_t nt = 1;
    const int32_t it = 0;
#endif /* _OPENMP */

    const int32_t m_s = m * it / nt;
    const int32_t m_e = m * (it + 1) / nt;
    const int32_t m_d = m_e - m_s;
    const int32_t n_s = n * it / nt;
    const int32_t n_e = n * (it + 1) / nt;
    const int32_t n_d = n_e - n_s;

    int32_t mode = 1;
    if ( n > nt ) {
        mode = 2;
    }
    int32_t iar, iac, ibr, ibc, icr, icc;
    if (transA == CblasNoTrans ) {
        iar = 1;
        iac = lda;
    } else {
        iar = lda;
        iac = 1;
    }
    if (transB == CblasNoTrans ) {
        ibr = 1;
        ibc = ldb;
    } else {
        ibr = ldb;
        ibc = 1;
    }
    if (order == CblasColMajor ) {
        icr = 1;
        icc = ldc;
    } else {
        icr = ldc;
        icc = 1;
    }

    if (order == CblasColMajor) {
        if ( mode == 1 ) {
            // split 'm'
            cblas_zgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iar, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_zgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibc, ldb, beta, c + n_s * icc, ldc);
        }
    } else {
        if ( mode == 1 ) {
            // split 'm'
            cblas_zgemm(order, transA, transB, m_d, n, k, alpha, a + m_s * iac, lda, b, ldb, beta, c + m_s * icr, ldc);
        } else {
            // split 'n'
            cblas_zgemm(order, transA, transB, m, n_d, k, alpha, a, lda, b + n_s * ibr, ldb, beta, c + n_s * icc, ldc);
        }
    }

    retrieve_fpe_flags(psw);
    return (uint64_t)NLCPY_ERROR_OK;
}


// Copyright (c) 2017, Lawrence Livermore National Security, LLC. Produced at
// the Lawrence Livermore National Laboratory. LLNL-CODE-734707. All Rights
// reserved. See files LICENSE and NOTICE for details.
//
// This file is part of CEED, a collection of benchmarks, miniapps, software
// libraries and APIs for efficient high-order finite element and spectral
// element discretizations for exascale applications. For more information and
// source code availability see http://github.com/ceed.
//
// The CEED research is supported by the Exascale Computing Project 17-SC-20-SC,
// a collaborative effort of two U.S. Department of Energy organizations (Office
// of Science and the National Nuclear Security Administration) responsible for
// the planning and preparation of a capable exascale ecosystem, including
// software, applications, hardware, advanced system engineering and early
// testbed platforms, in support of the nation's exascale computing imperative.

#ifndef setuparea_h
#define setuparea_h

#include <ceed.h>
#include <petsc.h>
#include <petscdmplex.h>
#include <petscfe.h>
#include <stdbool.h>
#include <string.h>
#include "qfunctions/area/areacube.h"
#include "qfunctions/area/areasphere.h"

#if PETSC_VERSION_LT(3,14,0)
#  define DMPlexGetClosureIndices(a,b,c,d,e,f,g,h,i) DMPlexGetClosureIndices(a,b,c,d,f,g,i)
#  define DMPlexRestoreClosureIndices(a,b,c,d,e,f,g,h,i) DMPlexRestoreClosureIndices(a,b,c,d,f,g,i)
#endif

#if PETSC_VERSION_LT(3,14,0)
#  define DMPlexCreateSphereMesh(a,b,c,d,e) DMPlexCreateSphereMesh(a,b,c,e)
#endif

// -----------------------------------------------------------------------------
// PETSc Operator Structs
// -----------------------------------------------------------------------------

// Data for PETSc
typedef struct User_ *User;
struct User_ {
  MPI_Comm comm;
  DM dm;
  Vec Xloc, Yloc, diag;
  CeedVector xceed, yceed;
  CeedOperator op;
  Ceed ceed;
};

// -----------------------------------------------------------------------------
// libCEED Data Struct
// -----------------------------------------------------------------------------

// libCEED data struct for level
typedef struct CeedData_ *CeedData;
struct CeedData_ {
  Ceed ceed;
  CeedBasis basisx, basisu;
  CeedElemRestriction Erestrictx, Erestrictu, Erestrictqdi;
  CeedQFunction qf_apply;
  CeedOperator op_apply, op_restrict, op_interp;
  CeedVector qdata, uceed, vceed;
};

// -----------------------------------------------------------------------------
// Problem Option Data
// -----------------------------------------------------------------------------

// Problem options
typedef enum {
  CUBE = 0, SPHERE = 1
} problemType;
static const char *const problemTypes[] = {"cube", "sphere",
                                           "problemType", "AREA", NULL
                                          };

// Problem specific data
typedef struct {
  CeedInt ncompu, ncompx, qdatasize, qextra, topodim;
  CeedQFunctionUser setupgeo, apply;
  const char *setupgeofname, *applyfname;
  CeedEvalMode inmode, outmode;
  CeedQuadMode qmode;
} problemData;

static problemData problemOptions[6] = {
  [CUBE] = {
    .ncompx = 3,
    .ncompu = 1,
    .topodim = 2,
    .qdatasize = 1,
    .qextra = 1,
    .setupgeo = SetupMassGeoCube,
    .apply = Mass,
    .setupgeofname = SetupMassGeoCube_loc,
    .applyfname = Mass_loc,
    .inmode = CEED_EVAL_INTERP,
    .outmode = CEED_EVAL_INTERP,
    .qmode = CEED_GAUSS
  },
  [SPHERE] = {
    .ncompx = 3,
    .ncompu = 1,
    .topodim = 2,
    .qdatasize = 1,
    .qextra = 1,
    .setupgeo = SetupMassGeoSphere,
    .apply = Mass,
    .setupgeofname = SetupMassGeoSphere_loc,
    .applyfname = Mass_loc,
    .inmode = CEED_EVAL_INTERP,
    .outmode = CEED_EVAL_INTERP,
    .qmode = CEED_GAUSS
  }
};

// -----------------------------------------------------------------------------
// PETSc sphere auxiliary functions
// -----------------------------------------------------------------------------

// Utility function taken from petsc/src/dm/impls/plex/examples/tutorials/ex7.c
static PetscErrorCode ProjectToUnitSphere(DM dm) {
  Vec            coordinates;
  PetscScalar   *coords;
  PetscInt       Nv, v, dim, d;
  PetscErrorCode ierr;

  PetscFunctionBeginUser;
  ierr = DMGetCoordinatesLocal(dm, &coordinates); CHKERRQ(ierr);
  ierr = VecGetLocalSize(coordinates, &Nv); CHKERRQ(ierr);
  ierr = VecGetBlockSize(coordinates, &dim); CHKERRQ(ierr);
  Nv  /= dim;
  ierr = VecGetArray(coordinates, &coords); CHKERRQ(ierr);
  for (v = 0; v < Nv; ++v) {
    PetscReal r = 0.0;

    for (d = 0; d < dim; ++d) r += PetscSqr(PetscRealPart(coords[v*dim+d]));
    r = PetscSqrtReal(r);
    for (d = 0; d < dim; ++d) coords[v*dim+d] /= r;
  }
  ierr = VecRestoreArray(coordinates, &coords); CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

// -----------------------------------------------------------------------------
// PETSc Finite Element space setup
// -----------------------------------------------------------------------------

// Create FE by degree
static int PetscFECreateByDegree(DM dm, PetscInt dim, PetscInt Nc,
                                 PetscBool isSimplex, const char prefix[],
                                 PetscInt order, PetscFE *fem) {
  PetscQuadrature q, fq;
  DM              K;
  PetscSpace      P;
  PetscDualSpace  Q;
  PetscInt        quadPointsPerEdge;
  PetscBool       tensor = isSimplex ? PETSC_FALSE : PETSC_TRUE;
  PetscErrorCode  ierr;

  PetscFunctionBeginUser;
  /* Create space */
  ierr = PetscSpaceCreate(PetscObjectComm((PetscObject) dm), &P); CHKERRQ(ierr);
  ierr = PetscObjectSetOptionsPrefix((PetscObject) P, prefix); CHKERRQ(ierr);
  ierr = PetscSpacePolynomialSetTensor(P, tensor); CHKERRQ(ierr);
  ierr = PetscSpaceSetFromOptions(P); CHKERRQ(ierr);
  ierr = PetscSpaceSetNumComponents(P, Nc); CHKERRQ(ierr);
  ierr = PetscSpaceSetNumVariables(P, dim); CHKERRQ(ierr);
  ierr = PetscSpaceSetDegree(P, order, order); CHKERRQ(ierr);
  ierr = PetscSpaceSetUp(P); CHKERRQ(ierr);
  ierr = PetscSpacePolynomialGetTensor(P, &tensor); CHKERRQ(ierr);
  /* Create dual space */
  ierr = PetscDualSpaceCreate(PetscObjectComm((PetscObject) dm), &Q);
  CHKERRQ(ierr);
  ierr = PetscDualSpaceSetType(Q,PETSCDUALSPACELAGRANGE); CHKERRQ(ierr);
  ierr = PetscObjectSetOptionsPrefix((PetscObject) Q, prefix); CHKERRQ(ierr);
  ierr = PetscDualSpaceCreateReferenceCell(Q, dim, isSimplex, &K); CHKERRQ(ierr);
  ierr = PetscDualSpaceSetDM(Q, K); CHKERRQ(ierr);
  ierr = DMDestroy(&K); CHKERRQ(ierr);
  ierr = PetscDualSpaceSetNumComponents(Q, Nc); CHKERRQ(ierr);
  ierr = PetscDualSpaceSetOrder(Q, order); CHKERRQ(ierr);
  ierr = PetscDualSpaceLagrangeSetTensor(Q, tensor); CHKERRQ(ierr);
  ierr = PetscDualSpaceSetFromOptions(Q); CHKERRQ(ierr);
  ierr = PetscDualSpaceSetUp(Q); CHKERRQ(ierr);
  /* Create element */
  ierr = PetscFECreate(PetscObjectComm((PetscObject) dm), fem); CHKERRQ(ierr);
  ierr = PetscObjectSetOptionsPrefix((PetscObject) *fem, prefix); CHKERRQ(ierr);
  ierr = PetscFESetFromOptions(*fem); CHKERRQ(ierr);
  ierr = PetscFESetBasisSpace(*fem, P); CHKERRQ(ierr);
  ierr = PetscFESetDualSpace(*fem, Q); CHKERRQ(ierr);
  ierr = PetscFESetNumComponents(*fem, Nc); CHKERRQ(ierr);
  ierr = PetscFESetUp(*fem); CHKERRQ(ierr);
  ierr = PetscSpaceDestroy(&P); CHKERRQ(ierr);
  ierr = PetscDualSpaceDestroy(&Q); CHKERRQ(ierr);
  /* Create quadrature */
  quadPointsPerEdge = PetscMax(order + 1,1);
  if (isSimplex) {
    ierr = PetscDTStroudConicalQuadrature(dim,   1, quadPointsPerEdge, -1.0, 1.0,
                                          &q); CHKERRQ(ierr);
    ierr = PetscDTStroudConicalQuadrature(dim-1, 1, quadPointsPerEdge, -1.0, 1.0,
                                          &fq); CHKERRQ(ierr);
  } else {
    ierr = PetscDTGaussTensorQuadrature(dim,   1, quadPointsPerEdge, -1.0, 1.0,
                                        &q); CHKERRQ(ierr);
    ierr = PetscDTGaussTensorQuadrature(dim-1, 1, quadPointsPerEdge, -1.0, 1.0,
                                        &fq); CHKERRQ(ierr);
  }
  ierr = PetscFESetQuadrature(*fem, q); CHKERRQ(ierr);
  ierr = PetscFESetFaceQuadrature(*fem, fq); CHKERRQ(ierr);
  ierr = PetscQuadratureDestroy(&q); CHKERRQ(ierr);
  ierr = PetscQuadratureDestroy(&fq); CHKERRQ(ierr);

  PetscFunctionReturn(0);
}

// -----------------------------------------------------------------------------
// PETSc Setup for Level
// -----------------------------------------------------------------------------

// This function sets up a DM for a given degree
static int SetupDMByDegree(DM dm, PetscInt degree, PetscInt ncompu,
                           PetscInt dim) {
  PetscInt ierr;
  PetscFE fe;

  PetscFunctionBeginUser;

  // Setup FE
  ierr = PetscFECreateByDegree(dm, dim, ncompu, PETSC_FALSE, NULL, degree, &fe);
  CHKERRQ(ierr);
  ierr = DMAddField(dm, NULL, (PetscObject)fe); CHKERRQ(ierr);

  // Setup DM
  ierr = DMCreateDS(dm); CHKERRQ(ierr);
  ierr = DMPlexSetClosurePermutationTensor(dm, PETSC_DETERMINE, NULL);
  CHKERRQ(ierr);
  ierr = PetscFEDestroy(&fe); CHKERRQ(ierr);

  PetscFunctionReturn(0);
}

// -----------------------------------------------------------------------------
// libCEED Setup for Level
// -----------------------------------------------------------------------------

// Destroy libCEED operator objects
static PetscErrorCode CeedDataDestroy(CeedData data) {
  PetscInt ierr;

  CeedVectorDestroy(&data->qdata);
  CeedVectorDestroy(&data->uceed);
  CeedVectorDestroy(&data->vceed);
  CeedBasisDestroy(&data->basisx);
  CeedBasisDestroy(&data->basisu);
  CeedElemRestrictionDestroy(&data->Erestrictu);
  CeedElemRestrictionDestroy(&data->Erestrictx);
  CeedElemRestrictionDestroy(&data->Erestrictqdi);
  CeedQFunctionDestroy(&data->qf_apply);
  CeedOperatorDestroy(&data->op_apply);
  ierr = PetscFree(data); CHKERRQ(ierr);

  PetscFunctionReturn(0);
}

// Auxiliary function to define CEED restrictions from DMPlex data
static int CreateRestrictionPlex(Ceed ceed, CeedInt P, CeedInt ncomp,
                                 CeedElemRestriction *Erestrict, DM dm) {
  PetscInt ierr;
  PetscInt c, cStart, cEnd, nelem, nnodes, *erestrict, eoffset;
  PetscSection section;
  Vec Uloc;

  PetscFunctionBegin;

  // Get Nelem
  ierr = DMGetSection(dm, &section); CHKERRQ(ierr);
  ierr = DMPlexGetHeightStratum(dm, 0, &cStart,& cEnd); CHKERRQ(ierr);
  nelem = cEnd - cStart;

  // Get indices
  ierr = PetscMalloc1(nelem*P*P, &erestrict); CHKERRQ(ierr);
  for (c=cStart, eoffset = 0; c<cEnd; c++) {
    PetscInt numindices, *indices, i;
    ierr = DMPlexGetClosureIndices(dm, section, section, c, PETSC_TRUE,
                                   &numindices, &indices, NULL, NULL);
    CHKERRQ(ierr);
    for (i=0; i<numindices; i+=ncomp) {
      for (PetscInt j=0; j<ncomp; j++) {
        if (indices[i+j] != indices[i] + (PetscInt)(copysign(j, indices[i])))
          SETERRQ1(PETSC_COMM_SELF, PETSC_ERR_ARG_INCOMP,
                   "Cell %D closure indices not interlaced", c);
      }
      // NO BC on closed surfaces
      PetscInt loc = indices[i];
      erestrict[eoffset++] = loc;
    }
    ierr = DMPlexRestoreClosureIndices(dm, section, section, c, PETSC_TRUE,
                                       &numindices, &indices, NULL, NULL);
    CHKERRQ(ierr);
  }

  // Setup CEED restriction
  ierr = DMGetLocalVector(dm, &Uloc); CHKERRQ(ierr);
  ierr = VecGetLocalSize(Uloc, &nnodes); CHKERRQ(ierr);

  ierr = DMRestoreLocalVector(dm, &Uloc); CHKERRQ(ierr);
  CeedElemRestrictionCreate(ceed, nelem, P*P, ncomp, 1, nnodes,
                            CEED_MEM_HOST, CEED_COPY_VALUES, erestrict,
                            Erestrict);
  ierr = PetscFree(erestrict); CHKERRQ(ierr);

  PetscFunctionReturn(0);
}

// Set up libCEED for a given degree
static int SetupLibceedByDegree(DM dm, Ceed ceed, CeedInt degree,
                                CeedInt topodim, CeedInt qextra,
                                PetscInt ncompx, PetscInt ncompu,
                                PetscInt xlsize, problemType problemChoice,
                                CeedData data) {
  int ierr;
  DM dmcoord;
  Vec coords;
  const PetscScalar *coordArray;
  CeedBasis basisx, basisu;
  CeedElemRestriction Erestrictx, Erestrictu, Erestrictqdi;
  CeedQFunction qf_setupgeo, qf_apply;
  CeedOperator op_setupgeo, op_apply;
  CeedVector xcoord, qdata, uceed, vceed;
  CeedInt P, Q, cStart, cEnd, nelem,
          qdatasize = problemOptions[problemChoice].qdatasize;

  // CEED bases
  P = degree + 1;
  Q = P + qextra;
  CeedBasisCreateTensorH1Lagrange(ceed, topodim, ncompu, P, Q,
                                  problemOptions[problemChoice].qmode, &basisu);
  CeedBasisCreateTensorH1Lagrange(ceed, topodim, ncompx, 2, Q,
                                  problemOptions[problemChoice].qmode, &basisx);

  // CEED restrictions
  ierr = DMGetCoordinateDM(dm, &dmcoord); CHKERRQ(ierr);
  ierr = DMPlexSetClosurePermutationTensor(dmcoord, PETSC_DETERMINE, NULL);
  CHKERRQ(ierr);

  ierr = CreateRestrictionPlex(ceed, 2, ncompx, &Erestrictx, dmcoord);
  CHKERRQ(ierr);
  ierr = CreateRestrictionPlex(ceed, P, ncompu, &Erestrictu, dm); CHKERRQ(ierr);

  ierr = DMPlexGetHeightStratum(dm, 0, &cStart, &cEnd); CHKERRQ(ierr);
  nelem = cEnd - cStart;

  CeedElemRestrictionCreateStrided(ceed, nelem, Q*Q, qdatasize,
                                   qdatasize*nelem*Q*Q,
                                   CEED_STRIDES_BACKEND, &Erestrictqdi);

  // Element coordinates
  ierr = DMGetCoordinatesLocal(dm, &coords); CHKERRQ(ierr);
  ierr = VecGetArrayRead(coords, &coordArray); CHKERRQ(ierr);

  CeedElemRestrictionCreateVector(Erestrictx, &xcoord, NULL);
  CeedVectorSetArray(xcoord, CEED_MEM_HOST, CEED_COPY_VALUES,
                     (PetscScalar *)coordArray);
  ierr = VecRestoreArrayRead(coords, &coordArray);

  // Create the vectors that will be needed in setup and apply
  CeedInt nqpts;
  CeedBasisGetNumQuadraturePoints(basisu, &nqpts);
  CeedVectorCreate(ceed, qdatasize*nelem*nqpts, &qdata);
  CeedVectorCreate(ceed, xlsize, &uceed);
  CeedVectorCreate(ceed, xlsize, &vceed);

  // Create the Q-function that builds the operator (i.e. computes its
  // quadrature data) and set its context data
  CeedQFunctionCreateInterior(ceed, 1, problemOptions[problemChoice].setupgeo,
                              problemOptions[problemChoice].setupgeofname,
                              &qf_setupgeo);
  CeedQFunctionAddInput(qf_setupgeo, "x", ncompx, CEED_EVAL_INTERP);
  CeedQFunctionAddInput(qf_setupgeo, "dx", ncompx*topodim, CEED_EVAL_GRAD);
  CeedQFunctionAddInput(qf_setupgeo, "weight", 1, CEED_EVAL_WEIGHT);
  CeedQFunctionAddOutput(qf_setupgeo, "qdata", qdatasize, CEED_EVAL_NONE);

  // Set up the mass operator
  CeedQFunctionCreateInterior(ceed, 1, problemOptions[problemChoice].apply,
                              problemOptions[problemChoice].applyfname,
                              &qf_apply);
  CeedQFunctionAddInput(qf_apply, "u", ncompu,
                        problemOptions[problemChoice].inmode);
  CeedQFunctionAddInput(qf_apply, "qdata", qdatasize, CEED_EVAL_NONE);
  CeedQFunctionAddOutput(qf_apply, "v", ncompu,
                         problemOptions[problemChoice].outmode);

  // Create the operator that builds the quadrature data for the operator
  CeedOperatorCreate(ceed, qf_setupgeo, NULL, NULL, &op_setupgeo);
  CeedOperatorSetField(op_setupgeo, "x", Erestrictx, basisx,
                       CEED_VECTOR_ACTIVE);
  CeedOperatorSetField(op_setupgeo, "dx", Erestrictx, basisx,
                       CEED_VECTOR_ACTIVE);
  CeedOperatorSetField(op_setupgeo, "weight", CEED_ELEMRESTRICTION_NONE, basisx,
                       CEED_VECTOR_NONE);
  CeedOperatorSetField(op_setupgeo, "qdata", Erestrictqdi,
                       CEED_BASIS_COLLOCATED, CEED_VECTOR_ACTIVE);

  // Create the mass or diff operator
  CeedOperatorCreate(ceed, qf_apply, NULL, NULL, &op_apply);
  CeedOperatorSetField(op_apply, "u", Erestrictu, basisu, CEED_VECTOR_ACTIVE);
  CeedOperatorSetField(op_apply, "qdata", Erestrictqdi, CEED_BASIS_COLLOCATED,
                       qdata);
  CeedOperatorSetField(op_apply, "v", Erestrictu, basisu, CEED_VECTOR_ACTIVE);

  // Setup qdata
  CeedOperatorApply(op_setupgeo, xcoord, qdata, CEED_REQUEST_IMMEDIATE);

  // Cleanup
  CeedQFunctionDestroy(&qf_setupgeo);
  CeedOperatorDestroy(&op_setupgeo);
  CeedVectorDestroy(&xcoord);

  // Save libCEED data
  data->basisx = basisx;
  data->basisu = basisu;
  data->Erestrictx = Erestrictx;
  data->Erestrictu = Erestrictu;
  data->Erestrictqdi = Erestrictqdi;
  data->qf_apply = qf_apply;
  data->op_apply = op_apply;
  data->qdata = qdata;
  data->uceed = uceed;
  data->vceed = vceed;

  PetscFunctionReturn(0);
}

#endif // setuparea_h


/* spswap.c
 *
 * Copyright (C) 2014 Patrick Alken
 * Copyright (C) 2016 Alexis Tantet
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <math.h>

#include <gsl/gsl_math.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_spmatrix.h>

#include "avl.c"

/*
gsl_spmatrix_transpose()
  Replace the sparse matrix src by its transpose,
keeping the matrix in the same storage format

Inputs: A - (input/output) sparse matrix to transpose.
*/

int
gsl_spmatrix_transpose(gsl_spmatrix * m)
{
  if (GSL_SPMATRIX_ISTRIPLET(m))
    {
      size_t n;

      /* swap row/column indices */
      for (n = 0; n < m->nz; ++n)
        {
          size_t tmp = m->p[n];
          m->p[n] = m->i[n];
          m->i[n] = tmp;
        }

      /* need to rebuild AVL tree, or element searches won't
       * work correctly with transposed indices */
      gsl_spmatrix_tree_rebuild(m);
    }
  else
    {
      GSL_ERROR("unknown sparse matrix type", GSL_EINVAL);
    }

  /* swap dimensions */
  if (m->size1 != m->size2)
    {
      size_t tmp = m->size1;
      m->size1 = m->size2;
      m->size2 = tmp;
    }

  return GSL_SUCCESS;
}

/*
gsl_spmatrix_transpose2()
  Replace the sparse matrix src by its transpose either by
  swapping its row and column indices if it is in triplet storage,
  or by switching its major if it is in compressed storage.

Inputs: A - (input/output) sparse matrix to transpose.
*/

int
gsl_spmatrix_transpose2(gsl_spmatrix * m)
{
  if (GSL_SPMATRIX_ISTRIPLET(m))
    {
      return gsl_spmatrix_transpose(m);
    }
  else if (GSL_SPMATRIX_ISCCS(m))
    {
      m->sptype = GSL_SPMATRIX_CRS;
    }
  else if (GSL_SPMATRIX_ISCRS(m))
    {
      m->sptype = GSL_SPMATRIX_CCS;
    }
  else
    {
      GSL_ERROR("unknown sparse matrix type", GSL_EINVAL);
    }

  /* swap dimensions */
  if (m->size1 != m->size2)
    {
      size_t tmp = m->size1;
      m->size1 = m->size2;
      m->size2 = tmp;
    }

  return GSL_SUCCESS;
}

int
gsl_spmatrix_transpose_memcpy(gsl_spmatrix *dest, const gsl_spmatrix *src)
{
  const size_t M = src->size1;
  const size_t N = src->size2;

  if (M != dest->size2 || N != dest->size1)
    {
      GSL_ERROR("dimensions of dest must be transpose of src matrix",
                GSL_EBADLEN);
    }
  else if (dest->sptype != src->sptype)
    {
      GSL_ERROR("cannot copy matrices of different storage formats",
                GSL_EINVAL);
    }
  else
    {
      int s = GSL_SUCCESS;
      const size_t nz = src->nz;

      if (dest->nzmax < src->nz)
        {
          s = gsl_spmatrix_realloc(src->nz, dest);
          if (s)
            return s;
        }

      if (GSL_SPMATRIX_ISTRIPLET(src))
        {
          size_t n;
          void *ptr;

          for (n = 0; n < nz; ++n)
            {
              dest->i[n] = src->p[n];
              dest->p[n] = src->i[n];
              dest->data[n] = src->data[n];

              /* copy binary tree data */
              ptr = avl_insert(dest->tree_data->tree, &dest->data[n]);
              if (ptr != NULL)
                {
                  GSL_ERROR("detected duplicate entry", GSL_EINVAL);
                }
            }
        }
      else if (GSL_SPMATRIX_ISCCS(src))
        {
          size_t *Ai = src->i;
          size_t *Ap = src->p;
          double *Ad = src->data;
          size_t *ATi = dest->i;
          size_t *ATp = dest->p;
          double *ATd = dest->data;
          size_t *w = (size_t *) dest->work;
          size_t p, j;

          /* initialize to 0 */
          for (p = 0; p < M + 1; ++p)
            ATp[p] = 0;

          /* compute row counts of A (= column counts for A^T) */
          for (p = 0; p < nz; ++p)
            ATp[Ai[p]]++;

          /* compute row pointers for A (= column pointers for A^T) */
          gsl_spmatrix_cumsum(M, ATp);

          /* make copy of row pointers */
          for (j = 0; j < M; ++j)
            w[j] = ATp[j];

          for (j = 0; j < N; ++j)
            {
              for (p = Ap[j]; p < Ap[j + 1]; ++p)
                {
                  size_t k = w[Ai[p]]++;
                  ATi[k] = j;
                  ATd[k] = Ad[p];
                }
            }
        }
      else if (GSL_SPMATRIX_ISCRS(src))
        {
          size_t *Aj = src->i;
          size_t *Ap = src->p;
          double *Ad = src->data;
          size_t *ATj = dest->i;
          size_t *ATp = dest->p;
          double *ATd = dest->data;
          size_t *w = (size_t *) dest->work;
          size_t p, i;

          /* initialize to 0 */
          for (p = 0; p < N + 1; ++p)
            ATp[p] = 0;

          /* compute column counts of A (= row counts for A^T) */
          for (p = 0; p < nz; ++p)
            ATp[Aj[p]]++;

          /* compute column pointers for A (= row pointers for A^T) */
          gsl_spmatrix_cumsum(N, ATp);

          /* make copy of column pointers */
          for (i = 0; i < N; ++i)
            w[i] = ATp[i];

          for (i = 0; i < M; ++i)
            {
              for (p = Ap[i]; p < Ap[i + 1]; ++p)
                {
                  size_t k = w[Aj[p]]++;
                  ATj[k] = i;
                  ATd[k] = Ad[p];
                }
            }
        }
      else
        {
          GSL_ERROR("unknown sparse matrix type", GSL_EINVAL);
        }

      dest->nz = nz;

      return s;
    }
} /* gsl_spmatrix_transpose_memcpy() */


/***************************************************************************/
/*                                                                         */
/* vector.c - Vector class for mruby                                       */
/* Copyright (C) 2015 Paolo Bosetti                                        */
/* paolo[dot]bosetti[at]unitn.it                                           */
/* Department of Industrial Engineering, University of Trento              */
/*                                                                         */
/* This library is free software.  You can redistribute it and/or          */
/* modify it under the terms of the GNU GENERAL PUBLIC LICENSE 2.0.        */
/*                                                                         */
/* This library is distributed in the hope that it will be useful,         */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of          */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           */
/* Artistic License 2.0 for more details.                                  */
/*                                                                         */
/* See the file LICENSE                                                    */
/*                                                                         */
/***************************************************************************/

#include <gsl/gsl_blas.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_sort_vector.h>
#include <gsl/gsl_rng.h>
#include "vector.h"

#pragma mark -
#pragma mark • Utilities

// Garbage collector handler, for play_data struct
// if play_data contains other dynamic data, free it too!
// Check it with GC.start
void vector_destructor(mrb_state *mrb, void *p_) {
  gsl_vector *v = (gsl_vector *)p_;
  gsl_vector_free(v);
};

// Creating data type and reference for GC, in a const struct
const struct mrb_data_type vector_data_type = {"vector_data",
                                               vector_destructor};

// Utility function for getting the struct out of the wrapping IV @data
void mrb_vector_get_data(mrb_state *mrb, mrb_value self, gsl_vector **data) {
  mrb_value data_value;
  data_value = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@data"));

  // Loading data from data_value into p_data:
  Data_Get_Struct(mrb, data_value, &vector_data_type, *data);
  if (!*data)
    mrb_raise(mrb, E_RUNTIME_ERROR, "Could not access @data");
}

#pragma mark -
#pragma mark • Init and accessing

// Data Initializer C function (not exposed!)
static void mrb_vector_init(mrb_state *mrb, mrb_value self, mrb_int n) {
  mrb_value data_value; // this IV holds the data
  gsl_vector *p_data;   // pointer to the C struct

  data_value = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@data"));

  // if @data already exists, free its content:
  if (!mrb_nil_p(data_value)) {
    Data_Get_Struct(mrb, data_value, &vector_data_type, p_data);
    free(p_data);
  }
  // Allocate and zero-out the data struct:
  p_data = gsl_vector_calloc(n);
  if (!p_data)
    mrb_raise(mrb, E_RUNTIME_ERROR, "Could not allocate @data");

  // Wrap struct into @data:
  mrb_iv_set(
      mrb, self, mrb_intern_lit(mrb, "@data"), // set @data
      mrb_obj_value(                           // with value hold in struct
          Data_Wrap_Struct(mrb, mrb->object_class, &vector_data_type, p_data)));
}

static mrb_value mrb_vector_initialize(mrb_state *mrb, mrb_value self) {
  mrb_int n;
  mrb_get_args(mrb, "i", &n);

  // Call strcut initializer:
  mrb_vector_init(mrb, self, n);
  mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "@length"), mrb_fixnum_value(n));
  mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "@format"),
             mrb_str_new_cstr(mrb, "%10.3f"));
  return mrb_nil_value();
}

static mrb_value mrb_vector_rnd_fill(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL;
  const gsl_rng_type *T;
  gsl_rng *r;
  mrb_int h;

  mrb_vector_get_data(mrb, self, &p_vec);

  gsl_rng_env_setup();
  T = gsl_rng_default;
  r = gsl_rng_alloc(T);

  for (h = 0; h < p_vec->size; h++) {
    gsl_vector_set(p_vec, h, gsl_rng_uniform(r));
  }
  return self;
}

static mrb_value mrb_vector_dup(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec = NULL, *p_vec_other = NULL;
  mrb_value args[1];

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  args[0] = mrb_fixnum_value(p_vec->size);
  other = mrb_obj_new(mrb, mrb_class_get(mrb, "Vector"), 1, args);
  mrb_vector_get_data(mrb, other, &p_vec_other);
  gsl_vector_memcpy(p_vec_other, p_vec);
  return other;
}

static mrb_value mrb_vector_all(mrb_state *mrb, mrb_value self) {
  mrb_float v;
  gsl_vector *p_vec = NULL;

  mrb_get_args(mrb, "f", &v);
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  gsl_vector_set_all(p_vec, v);
  return self;
}

static mrb_value mrb_vector_zero(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL;

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  gsl_vector_set_zero(p_vec);
  return self;
}

static mrb_value mrb_vector_basis(mrb_state *mrb, mrb_value self) {
  mrb_int i;
  gsl_vector *p_vec = NULL;

  mrb_get_args(mrb, "i", &i);
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  gsl_vector_set_basis(p_vec, i);
  return self;
}

#pragma mark -
#pragma mark • Tests

static mrb_value mrb_vector_equal(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec, *p_vec_other;
  mrb_get_args(mrb, "o", &other);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  mrb_vector_get_data(mrb, other, &p_vec_other);
  if (1 == gsl_vector_equal(p_vec, p_vec_other))
    return mrb_true_value();
  else
    return mrb_false_value();
}

#pragma mark -
#pragma mark • Accessors

static mrb_value mrb_vector_get_i(mrb_state *mrb, mrb_value self) {
  mrb_int i = 0;
  gsl_vector *p_vec = NULL;

  mrb_get_args(mrb, "i", &i);
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (i >= p_vec->size) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Vector index out of range!");
  }
  return mrb_float_value(mrb, gsl_vector_get(p_vec, (size_t)i));
}

static mrb_value mrb_vector_set_i(mrb_state *mrb, mrb_value self) {
  mrb_int i = 0;
  mrb_float f;
  gsl_vector *p_vec = NULL;

  mrb_get_args(mrb, "if", &i, &f);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (i >= p_vec->size) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Vector index out of range!");
  }
  gsl_vector_set(p_vec, (size_t)i, (double)f);
  return mrb_float_value(mrb, f);
}

static mrb_value mrb_vector_to_a(mrb_state *mrb, mrb_value self) {
  int i;
  mrb_value ary = mrb_nil_value();
  gsl_vector *p_vec = NULL;
  mrb_float e;
  mrb_vector_get_data(mrb, self, &p_vec);
  ary = mrb_ary_new_capa(mrb, p_vec->size);
  for (i = 0; i < p_vec->size; i++) {
    e = *(p_vec->data + i * p_vec->stride);
    mrb_ary_set(mrb, ary, i, mrb_float_value(mrb, e));
  }
  return ary;
}

#pragma mark -
#pragma mark • Properties

static mrb_value mrb_vector_max(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL;
  mrb_vector_get_data(mrb, self, &p_vec);
  return mrb_float_value(mrb, gsl_vector_max(p_vec));
}

static mrb_value mrb_vector_min(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL;
  mrb_vector_get_data(mrb, self, &p_vec);
  return mrb_float_value(mrb, gsl_vector_min(p_vec));
}

static mrb_value mrb_vector_max_index(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL;
  mrb_vector_get_data(mrb, self, &p_vec);
  return mrb_fixnum_value(gsl_vector_max_index(p_vec));
}

static mrb_value mrb_vector_min_index(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL;
  mrb_vector_get_data(mrb, self, &p_vec);
  return mrb_fixnum_value(gsl_vector_min_index(p_vec));
}

#pragma mark -
#pragma mark • Operations

static mrb_value mrb_vector_add(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec, *p_vec_other;
  mrb_get_args(mrb, "o", &other);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);

  if (mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Vector"))) {
    mrb_vector_get_data(mrb, other, &p_vec_other);
    if (p_vec->size != p_vec_other->size) {
      mrb_raise(mrb, E_VECTOR_ERROR, "Vector indexes don't match!");
    }
    gsl_vector_add(p_vec, p_vec_other);
  } else if (mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Numeric"))) {
    gsl_vector_add_constant(p_vec, mrb_to_flo(mrb, other));
  }
  return self;
}

static mrb_value mrb_vector_sub(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec, *p_vec_other;
  mrb_get_args(mrb, "o", &other);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  mrb_vector_get_data(mrb, other, &p_vec_other);
  if (p_vec->size != p_vec_other->size) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Vector dimensions don't match!");
  }
  gsl_vector_sub(p_vec, p_vec_other);
  return self;
}

static mrb_value mrb_vector_mul(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec, *p_vec_other;
  mrb_get_args(mrb, "o", &other);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Vector"))) {
    mrb_vector_get_data(mrb, other, &p_vec_other);
    if (p_vec->size != p_vec_other->size) {
      mrb_raise(mrb, E_VECTOR_ERROR, "Vector indexes don't match!");
    }
    gsl_vector_mul(p_vec, p_vec_other);
  } else if (mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Numeric"))) {
    gsl_vector_scale(p_vec, mrb_to_flo(mrb, other));
  }
  return self;
}

static mrb_value mrb_vector_div(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec, *p_vec_other;
  mrb_get_args(mrb, "o", &other);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  mrb_vector_get_data(mrb, other, &p_vec_other);
  if (p_vec->size != p_vec_other->size) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Vector indexes don't match!");
  }
  gsl_vector_div(p_vec, p_vec_other);
  return self;
}

static mrb_value mrb_vector_prod(mrb_state *mrb, mrb_value self) {
  mrb_value other;
  gsl_vector *p_vec, *p_vec_other;
  mrb_float res;
  mrb_get_args(mrb, "o", &other);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  mrb_vector_get_data(mrb, other, &p_vec_other);
  if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Vector"))) {
    mrb_raise(mrb, E_ARGUMENT_ERROR, "Need a Vector!");
  }
  if (p_vec->size != p_vec_other->size) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Vector indexes don't match!");
  }
  if (gsl_blas_ddot(p_vec, p_vec_other, &res)) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Cannot multiply");
  }
  return mrb_float_value(mrb, res);
}

static mrb_value mrb_vector_norm(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  return mrb_float_value(mrb, gsl_blas_dnrm2(p_vec));
}

static mrb_value mrb_vector_sum(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  return mrb_float_value(mrb, gsl_blas_dasum(p_vec));
}

static mrb_value mrb_vector_swap(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;
  mrb_int i, j;
  mrb_get_args(mrb, "ii", &i, &j);

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (gsl_vector_swap_elements(p_vec, i, j)) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Cannot swap");
  }
  return self;
}

static mrb_value mrb_vector_reverse(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;

  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (gsl_vector_reverse(p_vec)) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Cannot reverse");
  }
  return self;
}

#pragma mark -
#pragma mark • Statistics

static mrb_value mrb_vector_mean(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;
  mrb_float result;
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  result = gsl_stats_mean(p_vec->data, p_vec->stride, p_vec->size);
  return mrb_float_value(mrb, result);
}

static mrb_value mrb_vector_variance(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;
  mrb_float result;
  mrb_float m;
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (mrb_get_args(mrb, "|f", &m) == 1) {
    result = gsl_stats_variance_m(p_vec->data, p_vec->stride, p_vec->size, m);
  } else {
    result = gsl_stats_variance(p_vec->data, p_vec->stride, p_vec->size);
  }
  return mrb_float_value(mrb, result);
}

static mrb_value mrb_vector_sd(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;
  mrb_float result;
  mrb_float m;
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (mrb_get_args(mrb, "|f", &m) == 1) {
    result = gsl_stats_sd_m(p_vec->data, p_vec->stride, p_vec->size, m);
  } else {
    result = gsl_stats_sd(p_vec->data, p_vec->stride, p_vec->size);
  }
  return mrb_float_value(mrb, result);
}

static mrb_value mrb_vector_absdev(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec;
  mrb_float result;
  mrb_float m;
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  if (mrb_get_args(mrb, "|f", &m) == 1) {
    result = gsl_stats_absdev_m(p_vec->data, p_vec->stride, p_vec->size, m);
  } else {
    result = gsl_stats_absdev(p_vec->data, p_vec->stride, p_vec->size);
  }
  return mrb_float_value(mrb, result);
}

static mrb_value mrb_vector_quantile(mrb_state *mrb, mrb_value self) {
  gsl_vector *p_vec = NULL, *p_sort_vec = NULL;
  mrb_float result;
  mrb_float f;
  mrb_int n;
  // call utility for unwrapping @data into p_data:
  mrb_vector_get_data(mrb, self, &p_vec);
  n = mrb_get_args(mrb, "|f", &f);
  if (f < 0 || f > 1) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Quantile must be in [0,1]");
  }
  p_sort_vec = gsl_vector_calloc(p_vec->size);
  if (gsl_vector_memcpy(p_sort_vec, p_vec)) {
    mrb_raise(mrb, E_VECTOR_ERROR, "Cannot copy vector");
  }
  gsl_sort_vector(p_sort_vec);
  if (n == 1) {
    result = gsl_stats_quantile_from_sorted_data(
        p_sort_vec->data, p_sort_vec->stride, p_sort_vec->size, f);
  } else {
    result = gsl_stats_quantile_from_sorted_data(
        p_sort_vec->data, p_sort_vec->stride, p_sort_vec->size, 0.5);
  }
  return mrb_float_value(mrb, result);
}

#pragma mark -
#pragma mark • Gem setup

void mrb_gsl_vector_init(mrb_state *mrb) {
  struct RClass *gsl;

  mrb_load_string(mrb, "class VectorError < Exception; end");

  gsl = mrb_define_class(mrb, "Vector", mrb->object_class);
  mrb_define_method(mrb, gsl, "all", mrb_vector_all, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "zero", mrb_vector_zero, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "basis", mrb_vector_basis, MRB_ARGS_REQ(1));

  mrb_define_method(mrb, gsl, "initialize", mrb_vector_initialize,
                    MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "rnd_fill", mrb_vector_rnd_fill,
                    MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "dup", mrb_vector_dup, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "===", mrb_vector_equal, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "[]", mrb_vector_get_i, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "[]=", mrb_vector_set_i, MRB_ARGS_REQ(2));
  mrb_define_method(mrb, gsl, "to_a", mrb_vector_to_a, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "max", mrb_vector_max, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "min", mrb_vector_min, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "max_index", mrb_vector_max_index,
                    MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "min_index", mrb_vector_min_index,
                    MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "add!", mrb_vector_add, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "sub!", mrb_vector_sub, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "mul!", mrb_vector_mul, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "div!", mrb_vector_div, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "^", mrb_vector_prod, MRB_ARGS_REQ(1));
  mrb_define_method(mrb, gsl, "norm", mrb_vector_norm, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "sum", mrb_vector_sum, MRB_ARGS_NONE());
  mrb_define_method(mrb, gsl, "swap!", mrb_vector_swap, MRB_ARGS_REQ(2));
  mrb_define_method(mrb, gsl, "reverse!", mrb_vector_reverse, MRB_ARGS_NONE());

  mrb_define_method(mrb, gsl, "mean", mrb_vector_mean, MRB_ARGS_OPT(1));
  mrb_define_method(mrb, gsl, "variance", mrb_vector_variance, MRB_ARGS_OPT(1));
  mrb_define_method(mrb, gsl, "sd", mrb_vector_sd, MRB_ARGS_OPT(1));
  mrb_define_method(mrb, gsl, "absdev", mrb_vector_absdev, MRB_ARGS_OPT(1));
  mrb_define_method(mrb, gsl, "quantile", mrb_vector_quantile, MRB_ARGS_OPT(1));
}