#ifndef AFS_H
#define AFS_H
#include <gsl/gsl_matrix.h>
#include <glib.h>

//afsObject represents a two population coalescent
typedef struct afsObject
{
	int npops,nalleles;
	int aCounts[2]; //this keeps track of popn specific allele counts
	gsl_matrix_int **popMats;
	char *matString;

}
afsObject;

typedef struct afsStateSpace
{
	int nstates;
	afsObject **states;
	//below are only to hasten the process of state
	//space enumeration
	//int **nstateArray;
	//afsObject ****tmpStorage;
	GHashTable *stateHash;
}
afsStateSpace;

//for use in programs like cmc_island_mle
struct island_lik_params{
	int snpNumber, maxSampleSize,nstates,n1,n2, hStates,nParams;
	gsl_vector *weights, *sampleSizeVector,*rates;
	//paramVector for HMM == [N2,thetaState1,thetaState2,...,thetaStaten, m1State1,m2State1,m1State2,m2State2,...,m1Staten,m2Staten] <n is number of hmm states>;
	gsl_vector *paramVector,*paramCIUpper,*paramCILower, *mlParams;
	//For straight MLE the paramVector takes the form [N1,N2,m1,m2]
	gsl_matrix *log_afs, *posts, *transMat,*topol,*expAFS, *visitMat;
	gsl_matrix_int *moveType;
	afsStateSpace *stateSpace;
	struct site *data;
	double *cs_work;
	int *dim1, *dim2; //these pointers ease the construction of sparse matrices
	int nzCount;
	double maxLik;
	int optimized;

}island_lik_params;

#define MAXSTATES 100000000
#define MAXSIZE 20

afsObject *afsObjectNew(int n1, int n2);
void afsObjectFree(afsObject *v);
void afsObjectInit(afsObject *v,int n1, int n2);
void afsObjectPrint(afsObject *v);
afsObject *afsObjectNewFrom(afsObject *v);
afsObject *afsObjectDelta(afsObject *a, afsObject *b);
void afsObjectDeltaPre(afsObject *a, afsObject *b,afsObject *tmp);

int afsObjectCount(afsObject *a);

afsStateSpace *afsStateSpaceNew();
void afsStateSpaceFree(afsStateSpace *a);
int afsStateSpaceContainsState(afsStateSpace *S, afsObject *aState);
void afsStateSpaceRemoveAbsorbing(afsStateSpace *S);
int afsStateSpaceContainsStateTmpStorage(afsStateSpace *S, afsObject *aState);
void afsStateSpaceMapPopn(afsStateSpace *S, int *map);
void afsStateSpaceMapAndReducePopn(afsStateSpace *S, int *map, afsStateSpace *reducedSpace, int *reverseMap);

void afsStateSpaceRemovePopulation(afsStateSpace *S, int popn);
afsStateSpace *afsStateSpaceCopy(afsStateSpace *S);

void mcMatsImportFromFile(const char *fileName,int *nnz,double *topol, int *moveA, int *dim1, int *dim2);

int nDescPop1(afsObject *v);
int nDescPop2(afsObject *v);
int compatibleAfsGivenInitial(afsObject *v, int n1, int n2);
int afsObjectsEqual(afsObject *a, afsObject *b);
void acceptOrRejectProposedState(afsStateSpace *S, afsObject *child, int n1, int n2);
int afsObjectQsortCompare(const void *p1, const void *p2);

afsStateSpace *afsStateSpaceImportFromFile(const char *fileName);

void nonZeroEntries(gsl_matrix_int *m, int **coords, int *count);
void entriesGreaterThan(gsl_matrix_int *m, int **coords, int *count, int cut);
void entriesLessThan(gsl_matrix_int *m, int **coords, int *count, int cut);
int countNegativeEntries(gsl_matrix_int *m);

double xchoosey(int n, int k);

int matrixAllGTZ(gsl_matrix_int *m);

int matrixSum(gsl_matrix_int *m);
void gsl_matrix_prettyPrint(gsl_matrix *m);
double matrixSumDouble(gsl_matrix *m);

void preorderEvolveState(afsStateSpace *S, afsObject *aState, int n1, int n2);

void coalMarkovChainTopologyMatrix(afsStateSpace *S,gsl_matrix *topol, gsl_matrix_int *moveType, int n1, int n2);
int coalMarkovChainTopologyMatrix_2(afsStateSpace *S,gsl_matrix *topol, gsl_matrix_int *moveType, int *dim1, int *dim2);
int coalMarkovChainTopologyMatrix_sparse(afsStateSpace *S,double *topol, int *moveType, int *dim1, int *dim2);

void readTopolFile(const char *fileName, double *topA, int *moveA, int *dim1, int *dim2);

void fillTransitionMatrix(afsStateSpace *S, gsl_matrix *topol, gsl_matrix_int *moveType, gsl_matrix *transMat, gsl_vector *rates,
	double theta1, double theta2, double mig1, double mig2);
struct cs_di_sparse *fillTransitionMatrix_2(afsStateSpace *S, gsl_matrix *topol, gsl_matrix_int *moveType, gsl_vector *rates, int nzCount,
	int *dim1, int *dim2, double theta1, double theta2, double mig1, double mig2);

void fillExpectedAFS(afsStateSpace *S, gsl_matrix *transMat,gsl_vector *rates, gsl_matrix *expAFS, int n1, int n2);

void fillExpectedAFS_visits(afsStateSpace *S, gsl_matrix *visitMat,gsl_vector *rates, gsl_matrix *expAFS);
void fillExpectedAFS_visits_2(afsStateSpace *S, double *visitMat,gsl_vector *rates, gsl_matrix *expAFS);

void overlayPopnSplitting(afsStateSpace *S,gsl_matrix *topol, gsl_matrix_int *moveType, int n1, int n2);

void maximize_island_lik_const(double *lik, void * p);
void maximize_island_lik_hmm_const(double *lik, void * p);

void maximize_island_lik_CILower(double *lik, void * p);
void maximize_island_lik_CIUpper(double *lik, void * p);
void island_lik_constraint_CI(size_t n, const double *pGuess, void * p, double *result);

void island_lik_constraint(size_t n, const double *pGuess, void * p, double *result);
void island_lik_constraint_hmm(size_t n, const double *pGuess, void * p, double *result);

void fillLogAFS_ptr_sp(void * p);
void fillLogAFS_ptr_sp2(void * p);
void fillLogAFS_ptr_sp_hmm(int state,void * p);

void itoa(int n, char s[]);
void reverse(char s[]);

void NumericalGradient_island_lik(size_t n, const double *v, void *params, double *df);
void island_lik_fdf(const size_t n, const double *x,void *fparams,double *fval,double *grad);

#endif


// Copyright (c) 2013-2014 Anton Kozhevnikov, Thomas Schulthess
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that
// the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
//    following disclaimer.
// 2. 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.
//
// 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.

/** \file utils.h
 *
 *  \brief Contains definition and partial implementation of sirius::Utils class.
 */

#ifndef __UTILS_H__
#define __UTILS_H__

#include <gsl/gsl_sf_erf.h>
#include <fstream>
#include <string>
#include <complex>
#include "sirius_internal.h"
#include "typedefs.h"
#include "constants.h"
#include "sddk.hpp"

/// Utility class.
class Utils
{
    public:

        /// Maximum number of \f$ \ell, m \f$ combinations for a given \f$ \ell_{max} \f$
        static inline int lmmax(int lmax)
        {
            return (lmax + 1) * (lmax + 1);
        }

        static inline int lm_by_l_m(int l, int m)
        {
            return (l * l + l + m);
        }

        static inline int lmax_by_lmmax(int lmmax__)
        {
            int lmax = int(std::sqrt(double(lmmax__)) + 1e-8) - 1;
            if (lmmax(lmax) != lmmax__) {
                TERMINATE("wrong lmmax");
            }
            return lmax;
        }

        static inline bool file_exists(const std::string file_name)
        {
            std::ifstream ifs(file_name.c_str());
            return ifs.is_open();
        }

        static inline double fermi_dirac_distribution(double e)
        {
            double kT = 0.001;
            if (e > 100 * kT) return 0.0;
            if (e < -100 * kT) return 1.0;
            return (1.0 / (exp(e / kT) + 1.0));
        }

        static inline double gaussian_smearing(double e, double delta)
        {
            return 0.5 * (1 - gsl_sf_erf(e / delta));
        }

        static inline double cold_smearing(double e)
        {
            double a = -0.5634;

            if (e < -10.0) return 1.0;
            if (e > 10.0) return 0.0;

            return 0.5 * (1 - gsl_sf_erf(e)) - 1 - 0.25 * exp(-e * e) * (a + 2 * e - 2 * a * e * e) / sqrt(pi);
        }

        static std::string double_to_string(double val, int precision = -1)
        {
            char buf[100];

            double abs_val = std::abs(val);

            if (precision == -1)
            {
                if (abs_val > 1.0)
                {
                    precision = 6;
                }
                else if (abs_val > 1e-14)
                {
                    precision = int(-std::log(abs_val) / std::log(10.0)) + 7;
                }
                else
                {
                    return std::string("0.0");
                }
            }

            std::stringstream fmt;
            fmt << "%." << precision << "f";

            int len = snprintf(buf, 100, fmt.str().c_str(), val);
            for (int i = len - 1; i >= 1; i--)
            {
                if (buf[i] == '0' && buf[i - 1] == '0')
                {
                    buf[i] = 0;
                }
                else
                {
                    break;
                }
            }
            return std::string(buf);
        }

        static inline double phi_by_sin_cos(double sinp, double cosp)
        {
            double phi = std::atan2(sinp, cosp);
            if (phi < 0) {
                phi += twopi;
            }
            return phi;
        }

        static inline long double factorial(int n)
        {
            assert(n >= 0);

            long double result = 1.0L;
            for (int i = 1; i <= n; i++) {
                result *= i;
            }
            return result;
        }

        /// Simple hash function.
        /** Example: printf("hash: %16llX\n", hash()); */
        static uint64_t hash(void const* buff, size_t size, uint64_t h = 5381)
        {
            unsigned char const* p = static_cast<unsigned char const*>(buff);
            for(size_t i = 0; i < size; i++) {
                h = ((h << 5) + h) + p[i];
            }
            return h;
        }

        static void write_matrix(const std::string& fname,
                                 mdarray<double_complex, 2>& matrix,
                                 int nrow,
                                 int ncol,
                                 bool write_upper_only = true,
                                 bool write_abs_only = false,
                                 std::string fmt = "%18.12f")
        {
            static int icount = 0;

            if (nrow < 0 || nrow > (int)matrix.size(0) || ncol < 0 || ncol > (int)matrix.size(1))
                TERMINATE("wrong number of rows or columns");

            icount++;
            std::stringstream s;
            s << icount;
            std::string full_name = s.str() + "_" + fname;

            FILE* fout = fopen(full_name.c_str(), "w");

            for (int icol = 0; icol < ncol; icol++)
            {
                fprintf(fout, "column : %4i\n", icol);
                for (int i = 0; i < 80; i++) fprintf(fout, "-");
                fprintf(fout, "\n");
                if (write_abs_only)
                {
                    fprintf(fout, " row, absolute value\n");
                }
                else
                {
                    fprintf(fout, " row, real part, imaginary part, absolute value\n");
                }
                for (int i = 0; i < 80; i++) fprintf(fout, "-");
                fprintf(fout, "\n");

                int max_row = (write_upper_only) ? std::min(icol, nrow - 1) : (nrow - 1);
                for (int j = 0; j <= max_row; j++)
                {
                    if (write_abs_only)
                    {
                        std::string s = "%4i  " + fmt + "\n";
                        fprintf(fout, s.c_str(), j, abs(matrix(j, icol)));
                    }
                    else
                    {
                        fprintf(fout, "%4i  %18.12f %18.12f %18.12f\n", j, real(matrix(j, icol)), imag(matrix(j, icol)),
                                                                        abs(matrix(j, icol)));
                    }
                }
                fprintf(fout,"\n");
            }

            fclose(fout);
        }

        static void write_matrix(std::string const& fname,
                                 bool write_all,
                                 mdarray<double, 2>& matrix)
        {
            static int icount = 0;

            icount++;
            std::stringstream s;
            s << icount;
            std::string full_name = s.str() + "_" + fname;

            FILE* fout = fopen(full_name.c_str(), "w");

            for (int icol = 0; icol < (int)matrix.size(1); icol++)
            {
                fprintf(fout, "column : %4i\n", icol);
                for (int i = 0; i < 80; i++) fprintf(fout, "-");
                fprintf(fout, "\n");
                fprintf(fout, " row\n");
                for (int i = 0; i < 80; i++) fprintf(fout, "-");
                fprintf(fout, "\n");

                int max_row = (write_all) ? ((int)matrix.size(0) - 1) : std::min(icol, (int)matrix.size(0) - 1);
                for (int j = 0; j <= max_row; j++)
                {
                    fprintf(fout, "%4i  %18.12f\n", j, matrix(j, icol));
                }
                fprintf(fout,"\n");
            }

            fclose(fout);
        }

        static void write_matrix(std::string const& fname,
                                 bool write_all,
                                 matrix<double_complex> const& mtrx)
        {
            static int icount = 0;

            icount++;
            std::stringstream s;
            s << icount;
            std::string full_name = s.str() + "_" + fname;

            FILE* fout = fopen(full_name.c_str(), "w");

            for (int icol = 0; icol < (int)mtrx.size(1); icol++)
            {
                fprintf(fout, "column : %4i\n", icol);
                for (int i = 0; i < 80; i++) fprintf(fout, "-");
                fprintf(fout, "\n");
                fprintf(fout, " row\n");
                for (int i = 0; i < 80; i++) fprintf(fout, "-");
                fprintf(fout, "\n");

                int max_row = (write_all) ? ((int)mtrx.size(0) - 1) : std::min(icol, (int)mtrx.size(0) - 1);
                for (int j = 0; j <= max_row; j++)
                {
                    fprintf(fout, "%4i  %18.12f %18.12f\n", j, real(mtrx(j, icol)), imag(mtrx(j, icol)));
                }
                fprintf(fout,"\n");
            }

            fclose(fout);
        }

        template <typename T>
        static void check_hermitian(const std::string& name, matrix<T> const& mtrx, int n = -1)
        {
            assert(mtrx.size(0) == mtrx.size(1));

            double maxdiff = 0.0;
            int i0 = -1;
            int j0 = -1;

            if (n == -1) {
                n = static_cast<int>(mtrx.size(0));
            }

            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    double diff = std::abs(mtrx(i, j) - type_wrapper<T>::conjugate(mtrx(j, i)));
                    if (diff > maxdiff) {
                        maxdiff = diff;
                        i0 = i;
                        j0 = j;
                    }
                }
            }

            if (maxdiff > 1e-10) {
                std::stringstream s;
                s << name << " is not a symmetric or hermitian matrix" << std::endl
                  << "  maximum error: i, j : " << i0 << " " << j0 << " diff : " << maxdiff;

                WARNING(s);
            }
        }

        template <typename T>
        static inline double check_hermitian(dmatrix<T>& mtrx__, int n__)
        {
            dmatrix<T> tmp(n__, n__, mtrx__.blacs_grid(), mtrx__.bs_row(), mtrx__.bs_col());
            linalg<CPU>::tranc(n__, n__, mtrx__, 0, 0, tmp, 0, 0);

            splindex<block_cyclic> spl_r(n__, mtrx__.blacs_grid().num_ranks_row(), mtrx__.blacs_grid().rank_row(), mtrx__.bs_row());
            splindex<block_cyclic> spl_c(n__, mtrx__.blacs_grid().num_ranks_col(), mtrx__.blacs_grid().rank_col(), mtrx__.bs_col());

            double max_diff{0};
            for (int i = 0; i < spl_c.local_size(); i++) {
                for (int j = 0; j < spl_r.local_size(); j++) {
                    max_diff = std::max(max_diff, std::abs(mtrx__(j, i) - tmp(j, i)));
                }
            }
            mtrx__.blacs_grid().comm().template allreduce<double, mpi_op_t::max>(&max_diff, 1);
            return max_diff;
        }

        static double confined_polynomial(double r, double R, int p1, int p2, int dm)
        {
            double t = 1.0 - std::pow(r / R, 2);
            switch (dm)
            {
                case 0: {
                    return (std::pow(r, p1) * std::pow(t, p2));
                }
                case 2: {
                    return (-4 * p1 * p2 * std::pow(r, p1) * std::pow(t, p2 - 1) / std::pow(R, 2) +
                            p1 * (p1 - 1) * std::pow(r, p1 - 2) * std::pow(t, p2) +
                            std::pow(r, p1) * (4 * (p2 - 1) * p2 * std::pow(r, 2) * std::pow(t, p2 - 2) / std::pow(R, 4) -
                                          2 * p2 * std::pow(t, p2 - 1) / std::pow(R, 2)));
                }
                default: {
                    TERMINATE("wrong derivative order");
                    return 0.0;
                }
            }
        }

        static std::vector<int> l_by_lm(int lmax)
        {
            std::vector<int> v(lmmax(lmax));
            for (int l = 0; l <= lmax; l++) {
                for (int m = -l; m <= l; m++) {
                    v[lm_by_l_m(l, m)] = l;
                }
            }
            return std::move(v);
        }

        static std::vector<std::pair<int, int>> l_m_by_lm(int lmax)
        {
            std::vector<std::pair<int, int>> v(lmmax(lmax));
            for (int l = 0; l <= lmax; l++) {
                for (int m = -l; m <= l; m++) {
                    int lm = lm_by_l_m(l, m);
                    v[lm].first = l;
                    v[lm].second = m;
                }
            }
            return std::move(v);
        }

        inline static double round(double a__, int n__)
        {
            double a0 = std::floor(a__);
            double b = std::round((a__ - a0) * std::pow(10, n__)) / std::pow(10, n__);
            return a0 + b;
        }

        template <typename T>
        inline static int sign(T val)
        {
            return (T(0) < val) - (val < T(0));
        }

        static json serialize_timers() // TODO: here in wrong place; move somewhere else
        {
            json dict;

            /* collect local timers */
            for (auto& it: sddk::timer::timers()) {
                sddk::timer::timer_stats ts;

                ts.count = static_cast<int>(it.second.size());
                ts.total_value = 0.0;
                ts.min_value = 1e100;
                ts.max_value = 0.0;
                for (int i = 0; i < ts.count; i++) {
                    ts.total_value += it.second[i];
                    ts.min_value = std::min(ts.min_value, it.second[i]);
                    ts.max_value = std::max(ts.max_value, it.second[i]);
                }
                ts.average_value = (ts.count == 0) ? 0.0 : ts.total_value / ts.count;
                if (ts.count == 0) {
                    ts.min_value = 0.0;
                }

                dict[it.first] = {ts.total_value, ts.average_value, ts.min_value, ts.max_value};
            }
            return std::move(dict);
        }

};

#endif


// @(#)root/matrix:$Id$
// Authors: Fons Rademakers, Eddy Offermann   Nov 2003

/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#ifndef ROOT_TMatrixT
#define ROOT_TMatrixT

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// TMatrixT                                                             //
//                                                                      //
// Template class of a general matrix in the linear algebra package     //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

#include "TMatrixTBase.h"
#include "TMatrixTUtils.h"

#ifdef CBLAS
#include <vecLib/vBLAS.h>
//#include <cblas.h>
#endif

#include "Rtypes.h"
#include "TError.h"

template<class Element> class TMatrixTSym;
template<class Element> class TMatrixTSparse;
template<class Element> class TMatrixTLazy;

template<class Element> class TMatrixT : public TMatrixTBase<Element> {

protected:

   Element  fDataStack[TMatrixTBase<Element>::kSizeMax]; //! data container
   Element *fElements;                                   //[fNelems] elements themselves

   Element *New_m   (Int_t size);
   void     Delete_m(Int_t size,Element*&);
   Int_t    Memcpy_m(Element *newp,const Element *oldp,Int_t copySize,
                      Int_t newSize,Int_t oldSize);
   void     Allocate(Int_t nrows,Int_t ncols,Int_t row_lwb = 0,Int_t col_lwb = 0,Int_t init = 0,
                     Int_t /*nr_nonzeros*/ = -1);

public:

   enum {kWorkMax = 100};
   enum EMatrixCreatorsOp1 { kZero,kUnit,kTransposed,kInverted,kAtA };
   enum EMatrixCreatorsOp2 { kMult,kTransposeMult,kInvMult,kMultTranspose,kPlus,kMinus };

   TMatrixT(): fDataStack(), fElements(0) { }
   TMatrixT(Int_t nrows,Int_t ncols);
   TMatrixT(Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb);
   TMatrixT(Int_t nrows,Int_t ncols,const Element *data,Option_t *option="");
   TMatrixT(Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,const Element *data,Option_t *option="");
   TMatrixT(const TMatrixT      <Element> &another);
   TMatrixT(const TMatrixTSym   <Element> &another);
   TMatrixT(const TMatrixTSparse<Element> &another);
   template <class Element2> TMatrixT(const TMatrixT<Element2> &another): fElements(0)
   {
      R__ASSERT(another.IsValid());
      Allocate(another.GetNrows(),another.GetNcols(),another.GetRowLwb(),another.GetColLwb());
      *this = another;
   }

   TMatrixT(EMatrixCreatorsOp1 op,const TMatrixT<Element> &prototype);
   TMatrixT(const TMatrixT    <Element> &a,EMatrixCreatorsOp2 op,const TMatrixT   <Element> &b);
   TMatrixT(const TMatrixT    <Element> &a,EMatrixCreatorsOp2 op,const TMatrixTSym<Element> &b);
   TMatrixT(const TMatrixTSym <Element> &a,EMatrixCreatorsOp2 op,const TMatrixT   <Element> &b);
   TMatrixT(const TMatrixTSym <Element> &a,EMatrixCreatorsOp2 op,const TMatrixTSym<Element> &b);
   TMatrixT(const TMatrixTLazy<Element> &lazy_constructor);

   virtual ~TMatrixT() { Clear(); }

   // Elementary constructors

   void Plus (const TMatrixT   <Element> &a,const TMatrixT   <Element> &b);
   void Plus (const TMatrixT   <Element> &a,const TMatrixTSym<Element> &b);
   void Plus (const TMatrixTSym<Element> &a,const TMatrixT   <Element> &b) { Plus(b,a); }

   void Minus(const TMatrixT   <Element> &a,const TMatrixT   <Element> &b);
   void Minus(const TMatrixT   <Element> &a,const TMatrixTSym<Element> &b);
   void Minus(const TMatrixTSym<Element> &a,const TMatrixT   <Element> &b) { Minus(b,a); }

   void Mult (const TMatrixT   <Element> &a,const TMatrixT   <Element> &b);
   void Mult (const TMatrixT   <Element> &a,const TMatrixTSym<Element> &b);
   void Mult (const TMatrixTSym<Element> &a,const TMatrixT   <Element> &b);
   void Mult (const TMatrixTSym<Element> &a,const TMatrixTSym<Element> &b);

   void TMult(const TMatrixT   <Element> &a,const TMatrixT   <Element> &b);
   void TMult(const TMatrixT   <Element> &a,const TMatrixTSym<Element> &b);
   void TMult(const TMatrixTSym<Element> &a,const TMatrixT   <Element> &b) { Mult(a,b); }
   void TMult(const TMatrixTSym<Element> &a,const TMatrixTSym<Element> &b) { Mult(a,b); }

   void MultT(const TMatrixT   <Element> &a,const TMatrixT   <Element> &b);
   void MultT(const TMatrixT   <Element> &a,const TMatrixTSym<Element> &b) { Mult(a,b); }
   void MultT(const TMatrixTSym<Element> &a,const TMatrixT   <Element> &b);
   void MultT(const TMatrixTSym<Element> &a,const TMatrixTSym<Element> &b) { Mult(a,b); }

   virtual const Element *GetMatrixArray  () const;
   virtual       Element *GetMatrixArray  ();
   virtual const Int_t   *GetRowIndexArray() const { return 0; }
   virtual       Int_t   *GetRowIndexArray()       { return 0; }
   virtual const Int_t   *GetColIndexArray() const { return 0; }
   virtual       Int_t   *GetColIndexArray()       { return 0; }

   virtual       TMatrixTBase<Element> &SetRowIndexArray(Int_t * /*data*/) { MayNotUse("SetRowIndexArray(Int_t *)"); return *this; }
   virtual       TMatrixTBase<Element> &SetColIndexArray(Int_t * /*data*/) { MayNotUse("SetColIndexArray(Int_t *)"); return *this; }

   virtual void Clear(Option_t * /*option*/ ="") { if (this->fIsOwner) Delete_m(this->fNelems,fElements);
                                                   else fElements = 0;
                                                   this->fNelems = 0; }

           TMatrixT    <Element> &Use     (Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,Element *data);
   const   TMatrixT    <Element> &Use     (Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,const Element *data) const
                                            { return (const TMatrixT<Element>&)
                                                     ((const_cast<TMatrixT<Element> *>(this))->Use(row_lwb,row_upb,col_lwb,col_upb, const_cast<Element *>(data))); }
           TMatrixT    <Element> &Use     (Int_t nrows,Int_t ncols,Element *data);
   const   TMatrixT    <Element> &Use     (Int_t nrows,Int_t ncols,const Element *data) const;
           TMatrixT    <Element> &Use     (TMatrixT<Element> &a);
   const   TMatrixT    <Element> &Use     (const TMatrixT<Element> &a) const;

   virtual TMatrixTBase<Element> &GetSub  (Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,
                                           TMatrixTBase<Element> &target,Option_t *option="S") const;
           TMatrixT    <Element>  GetSub  (Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,Option_t *option="S") const;
   virtual TMatrixTBase<Element> &SetSub  (Int_t row_lwb,Int_t col_lwb,const TMatrixTBase<Element> &source);

   virtual TMatrixTBase<Element> &ResizeTo(Int_t nrows,Int_t ncols,Int_t /*nr_nonzeros*/ =-1);
   virtual TMatrixTBase<Element> &ResizeTo(Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,Int_t /*nr_nonzeros*/ =-1);
   inline  TMatrixTBase<Element> &ResizeTo(const TMatrixT<Element> &m) {
                                            return ResizeTo(m.GetRowLwb(),m.GetRowUpb(),m.GetColLwb(),m.GetColUpb());
                                 }

   virtual Double_t Determinant  () const;
   virtual void     Determinant  (Double_t &d1,Double_t &d2) const;

           TMatrixT<Element> &Invert      (Double_t *det=0);
           TMatrixT<Element> &InvertFast  (Double_t *det=0);
           TMatrixT<Element> &Transpose   (const TMatrixT<Element> &source);
   inline  TMatrixT<Element> &T           () { return this->Transpose(*this); }
           TMatrixT<Element> &Rank1Update (const TVectorT<Element> &v,Element alpha=1.0);
           TMatrixT<Element> &Rank1Update (const TVectorT<Element> &v1,const TVectorT<Element> &v2,Element alpha=1.0);
           Element            Similarity  (const TVectorT<Element> &v) const;

   TMatrixT<Element> &NormByColumn(const TVectorT<Element> &v,Option_t *option="D");
   TMatrixT<Element> &NormByRow   (const TVectorT<Element> &v,Option_t *option="D");

   // Either access a_ij as a(i,j)
   inline       Element                     operator()(Int_t rown,Int_t coln) const;
   inline       Element                    &operator()(Int_t rown,Int_t coln);

   // or as a[i][j]
   inline const TMatrixTRow_const<Element>  operator[](Int_t rown) const { return TMatrixTRow_const<Element>(*this,rown); }
   inline       TMatrixTRow      <Element>  operator[](Int_t rown)       { return TMatrixTRow      <Element>(*this,rown); }

   TMatrixT<Element> &operator= (const TMatrixT      <Element> &source);
   TMatrixT<Element> &operator= (const TMatrixTSym   <Element> &source);
   TMatrixT<Element> &operator= (const TMatrixTSparse<Element> &source);
   TMatrixT<Element> &operator= (const TMatrixTLazy  <Element> &source);
   template <class Element2> TMatrixT<Element> &operator= (const TMatrixT<Element2> &source)
   {
      if (!AreCompatible(*this,source)) {
         Error("operator=(const TMatrixT2 &)","matrices not compatible");
         return *this;
      }

     TObject::operator=(source);
     const Element2 * const ps = source.GetMatrixArray();
           Element  * const pt = this->GetMatrixArray();
     for (Int_t i = 0; i < this->fNelems; i++)
        pt[i] = ps[i];
     this->fTol = source.GetTol();
     return *this;
   }

   TMatrixT<Element> &operator= (Element val);
   TMatrixT<Element> &operator-=(Element val);
   TMatrixT<Element> &operator+=(Element val);
   TMatrixT<Element> &operator*=(Element val);

   TMatrixT<Element> &operator+=(const TMatrixT   <Element> &source);
   TMatrixT<Element> &operator+=(const TMatrixTSym<Element> &source);
   TMatrixT<Element> &operator-=(const TMatrixT   <Element> &source);
   TMatrixT<Element> &operator-=(const TMatrixTSym<Element> &source);

   TMatrixT<Element> &operator*=(const TMatrixT            <Element> &source);
   TMatrixT<Element> &operator*=(const TMatrixTSym         <Element> &source);
   TMatrixT<Element> &operator*=(const TMatrixTDiag_const  <Element> &diag);
   TMatrixT<Element> &operator/=(const TMatrixTDiag_const  <Element> &diag);
   TMatrixT<Element> &operator*=(const TMatrixTRow_const   <Element> &row);
   TMatrixT<Element> &operator/=(const TMatrixTRow_const   <Element> &row);
   TMatrixT<Element> &operator*=(const TMatrixTColumn_const<Element> &col);
   TMatrixT<Element> &operator/=(const TMatrixTColumn_const<Element> &col);

   const TMatrixT<Element> EigenVectors(TVectorT<Element> &eigenValues) const;

   ClassDef(TMatrixT,4) // Template of General Matrix class
};

#ifndef __CINT__
// When building with -fmodules, it instantiates all pending instantiations,
// instead of delaying them until the end of the translation unit.
// We 'got away with' probably because the use and the definition of the
// explicit specialization do not occur in the same TU.
//
// In case we are building with -fmodules, we need to forward declare the
// specialization in order to compile the dictionary G__Matrix.cxx.
template <> TClass *TMatrixT<double>::Class();
#endif // __CINT__

template <class Element> inline const Element           *TMatrixT<Element>::GetMatrixArray() const { return fElements; }
template <class Element> inline       Element           *TMatrixT<Element>::GetMatrixArray()       { return fElements; }

template <class Element> inline       TMatrixT<Element> &TMatrixT<Element>::Use           (Int_t nrows,Int_t ncols,Element *data)
                                                                                { return Use(0,nrows-1,0,ncols-1,data); }
template <class Element> inline const TMatrixT<Element> &TMatrixT<Element>::Use           (Int_t nrows,Int_t ncols,const Element *data) const
                                                                                { return Use(0,nrows-1,0,ncols-1,data); }
template <class Element> inline       TMatrixT<Element> &TMatrixT<Element>::Use           (TMatrixT &a)
                                                                                {
                                                                                R__ASSERT(a.IsValid());
                                                                                return Use(a.GetRowLwb(),a.GetRowUpb(),
                                                                                a.GetColLwb(),a.GetColUpb(),a.GetMatrixArray());
                                                                                }
template <class Element> inline const TMatrixT<Element> &TMatrixT<Element>::Use           (const TMatrixT &a) const
                                                                                {
                                                                                R__ASSERT(a.IsValid());
                                                                                return Use(a.GetRowLwb(),a.GetRowUpb(),
                                                                                a.GetColLwb(),a.GetColUpb(),a.GetMatrixArray());
                                                                                }

template <class Element> inline       TMatrixT<Element>  TMatrixT<Element>::GetSub        (Int_t row_lwb,Int_t row_upb,Int_t col_lwb,Int_t col_upb,
                                                                                Option_t *option) const
                                                                                {
                                                                                TMatrixT tmp;
                                                                                this->GetSub(row_lwb,row_upb,col_lwb,col_upb,tmp,option);
                                                                                return tmp;
                                                                                }

template <class Element> inline Element TMatrixT<Element>::operator()(Int_t rown,Int_t coln) const
{
   R__ASSERT(this->IsValid());
   const Int_t arown = rown-this->fRowLwb;
   const Int_t acoln = coln-this->fColLwb;
   if (arown >= this->fNrows || arown < 0) {
      Error("operator()","Request row(%d) outside matrix range of %d - %d",rown,this->fRowLwb,this->fRowLwb+this->fNrows);
      return TMatrixTBase<Element>::NaNValue();
   }
   if (acoln >= this->fNcols || acoln < 0) {
      Error("operator()","Request column(%d) outside matrix range of %d - %d",coln,this->fColLwb,this->fColLwb+this->fNcols);
      return TMatrixTBase<Element>::NaNValue();

   }
   return (fElements[arown*this->fNcols+acoln]);
}

template <class Element> inline Element &TMatrixT<Element>::operator()(Int_t rown,Int_t coln)
{
   R__ASSERT(this->IsValid());
   const Int_t arown = rown-this->fRowLwb;
   const Int_t acoln = coln-this->fColLwb;
   if (arown >= this->fNrows || arown < 0) {
      Error("operator()","Request row(%d) outside matrix range of %d - %d",rown,this->fRowLwb,this->fRowLwb+this->fNrows);
      return TMatrixTBase<Element>::NaNValue();
   }
   if (acoln >= this->fNcols || acoln < 0) {
      Error("operator()","Request column(%d) outside matrix range of %d - %d",coln,this->fColLwb,this->fColLwb+this->fNcols);
      return TMatrixTBase<Element>::NaNValue();
   }
   return (fElements[arown*this->fNcols+acoln]);
}

template <class Element> TMatrixT<Element>  operator+  (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator+  (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator+  (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator+  (const TMatrixT   <Element> &source ,      Element               val    );
template <class Element> TMatrixT<Element>  operator+  (      Element               val    ,const TMatrixT   <Element> &source );
template <class Element> TMatrixT<Element>  operator-  (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator-  (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator-  (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator-  (const TMatrixT   <Element> &source ,      Element               val    );
template <class Element> TMatrixT<Element>  operator-  (      Element               val    ,const TMatrixT   <Element> &source );
template <class Element> TMatrixT<Element>  operator*  (      Element               val    ,const TMatrixT   <Element> &source );
template <class Element> TMatrixT<Element>  operator*  (const TMatrixT   <Element> &source ,      Element               val    );
template <class Element> TMatrixT<Element>  operator*  (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator*  (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator*  (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator*  (const TMatrixTSym<Element> &source1,const TMatrixTSym<Element> &source2);
// Preventing warnings with -Weffc++ in GCC since overloading the || and && operators was a design choice.
#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40600
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#endif
template <class Element> TMatrixT<Element>  operator&& (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator&& (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator&& (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator|| (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator|| (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator|| (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40600
#pragma GCC diagnostic pop
#endif
template <class Element> TMatrixT<Element>  operator>  (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator>  (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator>  (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator>= (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator>= (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator>= (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator<= (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator<= (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator<= (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator<  (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator<  (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator<  (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator!= (const TMatrixT   <Element> &source1,const TMatrixT   <Element> &source2);
template <class Element> TMatrixT<Element>  operator!= (const TMatrixT   <Element> &source1,const TMatrixTSym<Element> &source2);
template <class Element> TMatrixT<Element>  operator!= (const TMatrixTSym<Element> &source1,const TMatrixT   <Element> &source2);

template <class Element> TMatrixT<Element> &Add        (TMatrixT<Element> &target,      Element               scalar,const TMatrixT   <Element> &source);
template <class Element> TMatrixT<Element> &Add        (TMatrixT<Element> &target,      Element               scalar,const TMatrixTSym<Element> &source);
template <class Element> TMatrixT<Element> &ElementMult(TMatrixT<Element> &target,const TMatrixT   <Element> &source);
template <class Element> TMatrixT<Element> &ElementMult(TMatrixT<Element> &target,const TMatrixTSym<Element> &source);
template <class Element> TMatrixT<Element> &ElementDiv (TMatrixT<Element> &target,const TMatrixT   <Element> &source);
template <class Element> TMatrixT<Element> &ElementDiv (TMatrixT<Element> &target,const TMatrixTSym<Element> &source);

template <class Element> void AMultB (const Element * const ap,Int_t na,Int_t ncolsa,
                                      const Element * const bp,Int_t nb,Int_t ncolsb,Element *cp);
template <class Element> void AtMultB(const Element * const ap,Int_t ncolsa,
                                      const Element * const bp,Int_t nb,Int_t ncolsb,Element *cp);
template <class Element> void AMultBt(const Element * const ap,Int_t na,Int_t ncolsa,
                                      const Element * const bp,Int_t nb,Int_t ncolsb,Element *cp);

#endif


#include <jni.h>
#include <omp.h>
#ifdef __INTEL_COMPILER
#include <mkl.h>
#include <mkl_trans.h>
#else
#include <cblas.h>
#endif

JNIEXPORT jdouble JNICALL Java_edu_berkeley_bid_CBLAS_ddot
(JNIEnv * env, jobject calling_obj, jint N, jdoubleArray jX, jint incX, jdoubleArray jY, jint incY){
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jdouble returnValue;

	returnValue = cblas_ddot(N, X, incX, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	return returnValue;
}

JNIEXPORT jdouble JNICALL Java_edu_berkeley_bid_CBLAS_ddotxx
(JNIEnv * env, jobject calling_obj, jint N, jdoubleArray jX, jint startX, jdoubleArray jY, jint startY){
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jdouble returnValue;

	returnValue = cblas_ddot(N, X+startX, 1, Y+startY, 1);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	return returnValue;
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_ddotm
(JNIEnv * env, jobject calling_obj, jint nrows, jint ncols, jdoubleArray jX, jint ldx, jdoubleArray jY, jint ldy, jdoubleArray jZ){
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jdouble * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);
        int i;

    for (i = 0; i < ncols; i++) {
      Z[i] = cblas_ddot(nrows, X+i*ldx, 1, Y+i*ldy, 1);
    }

	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_ddotr
(JNIEnv * env, jobject calling_obj, jint nrows, jint ncols, jdoubleArray jX, jint ldx, jdoubleArray jY, jint ldy, jdoubleArray jZ){
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jdouble * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);
    int i, j;

    for (i = 0; i < ncols; i++) {
#pragma omp parallel for
      for (j = 0; j < nrows; j++) {
        Z[j] += X[j + i*ldx] * Y[j + i*ldy];
      }
    }

	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_daxpy
(JNIEnv * env, jobject calling_obj, jint N, jdouble a, jdoubleArray jX, jint incX, jdoubleArray jY, jint incY){
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);

        cblas_daxpy(N, a, X, incX, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_daxpyxx
(JNIEnv * env, jobject calling_obj, jint N, jdouble a, jdoubleArray jX, jint startX, jdoubleArray jY, jint startY){
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);

        cblas_daxpy(N, a, X+startX, 1, Y+startY, 1);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_dgemv
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint M, jint N, jdouble alpha,
 jdoubleArray jA, jint lda, jdoubleArray jX, jint incX, jdouble beta, jdoubleArray jY, jint incY){
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jdouble * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jdouble * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);

	cblas_dgemv((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, M, N, alpha, A, lda, X, incX, beta, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_dgemm
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint transB, jint M, jint N, jint K,
 jdouble alpha, jdoubleArray jA, jint lda, jdoubleArray jB, jint ldb, jdouble beta, jdoubleArray jC, jint ldc){
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jdouble * B = (*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE);
	jdouble * C = (*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE);

	cblas_dgemm((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, (CBLAS_TRANSPOSE)transB, M, N, K,
                    alpha, A, lda, B, ldb, beta, C, ldc);

	(*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_domatcopy
(JNIEnv * env, jobject calling_obj, jstring j_order, jstring j_transA, jint M, jint N,
 jdouble alpha, jdoubleArray j_A, jint lda, jdoubleArray j_B, jint ldb) {
	char * order = (char *)(*env)->GetStringUTFChars(env, j_order, 0);
	char * transA = (char *)(*env)->GetStringUTFChars(env, j_transA, 0);
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jdouble * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);

#ifdef __INTEL_COMPILER
	mkl_domatcopy(order[0], transA[0], M, N, alpha, A, lda, B, ldb);
#else
	int corder = (order[0] == 'r' || order[0] == 'R') ? CblasRowMajor : CblasColMajor;
	int ctrans = (transA[0] == 't' || transA[0] == 'T') ? CblasTrans : CblasNoTrans;
	cblas_domatcopy(corder, ctrans, M, N, alpha, A, lda, B, ldb);
#endif

	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
	(*env)->ReleaseStringUTFChars(env, j_transA, transA);
	(*env)->ReleaseStringUTFChars(env, j_order, order);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_dmcscm
(JNIEnv * env, jobject calling_obj, jint M, jint N, jdoubleArray j_A, jint lda,
 jdoubleArray j_B, jintArray j_ir, jintArray j_jc, jdoubleArray j_C, jint ldc){
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jdouble * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);
	jint * ir = (*env)->GetPrimitiveArrayCritical(env, j_ir, JNI_FALSE);
	jint * jc = (*env)->GetPrimitiveArrayCritical(env, j_jc, JNI_FALSE);
	jdouble * C = (*env)->GetPrimitiveArrayCritical(env, j_C, JNI_FALSE);

        int ioff = jc[0];
        int i;
#pragma omp parallel for
        for (i = 0; i < N; i++) {
          int j, ir0;
          for (j = jc[i]-ioff; j < jc[i+1]-ioff; j++) {
            ir0 = ir[j]-ioff;
            cblas_daxpy(M, B[j], A+(ir0*lda), 1, C+(i*ldc), 1);
          }
        }

	(*env)->ReleasePrimitiveArrayCritical(env, j_C, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_jc, jc, 0);
    (*env)->ReleasePrimitiveArrayCritical(env, j_ir, ir, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_dmcsrm
(JNIEnv * env, jobject calling_obj, jint M, jint N, jdoubleArray j_A, jint lda,
 jdoubleArray j_B, jintArray j_ir, jintArray j_jc, jdoubleArray j_C, jint ldc){
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jdouble * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);
	jint * ir = (*env)->GetPrimitiveArrayCritical(env, j_ir, JNI_FALSE);
	jint * jc = (*env)->GetPrimitiveArrayCritical(env, j_jc, JNI_FALSE);
	jdouble * C = (*env)->GetPrimitiveArrayCritical(env, j_C, JNI_FALSE);

    int ioff = jc[0];
    int i;
#pragma omp parallel for
    for (i = 0; i < N; i++) {
      int j, k;
      for (j = jc[i]-ioff; j < jc[i+1]-ioff; j++) {
        k = ir[j]-ioff;
        cblas_daxpy(M, B[j], A+(i*lda), 1, C+(k*ldc), 1);
      }
    }

	(*env)->ReleasePrimitiveArrayCritical(env, j_C, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_jc, jc, 0);
    (*env)->ReleasePrimitiveArrayCritical(env, j_ir, ir, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT jfloat JNICALL Java_edu_berkeley_bid_CBLAS_sdot
(JNIEnv * env, jobject calling_obj, jint N, jfloatArray jX, jint incX, jfloatArray jY, jint incY){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat returnValue;

	returnValue = cblas_sdot(N, X, incX, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	return returnValue;
}

JNIEXPORT jfloat JNICALL Java_edu_berkeley_bid_CBLAS_sdotxx
(JNIEnv * env, jobject calling_obj, jint N, jfloatArray jX, jint startX, jfloatArray jY, jint startY){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat returnValue;

	returnValue = cblas_sdot(N, X+startX, 1, Y+startY, 1);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	return returnValue;
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_sdotm
(JNIEnv * env, jobject calling_obj, jint nrows, jint ncols, jfloatArray jX, jint ldx, jfloatArray jY, jint ldy, jfloatArray jZ){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);
    int i, j;

    for (i = 0; i < ncols; i++) {
      Z[i] = cblas_sdot(nrows, X+i*ldx, 1, Y+i*ldy, 1);
    }

	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_sdotr
(JNIEnv * env, jobject calling_obj, jint nrows, jint ncols, jfloatArray jX, jint ldx, jfloatArray jY, jint ldy, jfloatArray jZ){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);
    int i, j;

    for (i = 0; i < ncols; i++) {
#pragma omp parallel for
      for (j = 0; j < nrows; j++) {
        Z[j] += X[j + i*ldx] * Y[j + i*ldy];
      }
    }

	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_sgemv
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint M, jint N, jfloat alpha,
jfloatArray jA, jint lda, jfloatArray jX, jint incX, jfloat beta, jfloatArray jY, jint incY){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);

	cblas_sgemv((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, M, N, alpha, A, lda, X, incX, beta, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_sgemm
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint transB, jint M, jint N, jint K,
 jfloat alpha, jfloatArray jA, jint lda, jfloatArray jB, jint ldb, jfloat beta, jfloatArray jC, jint ldc){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE);
	jfloat * C = (*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE);

	cblas_sgemm((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, (CBLAS_TRANSPOSE)transB, M, N, K,
                    alpha, A, lda, B, ldb, beta, C, ldc);

	(*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_sgemmx
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint transB, jint M, jint N, jint K,
 jfloat alpha, jfloatArray jA, jint Aoff, jint lda, jfloatArray jB, jint Boff, jint ldb, jfloat beta, jfloatArray jC, jint Coff, jint ldc){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE);
	jfloat * C = (*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE);

	cblas_sgemm((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, (CBLAS_TRANSPOSE)transB, M, N, K,
                    alpha, A+Aoff, lda, B+Boff, ldb, beta, C+Coff, ldc);

	(*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_somatcopy
(JNIEnv * env, jobject calling_obj, jstring j_order, jstring j_transA, jint M, jint N,
 jfloat alpha, jfloatArray j_A, jint lda, jfloatArray j_B, jint ldb) {
	char * order = (char *)(*env)->GetStringUTFChars(env, j_order, 0);
	char * transA = (char *)(*env)->GetStringUTFChars(env, j_transA, 0);
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);

#ifdef __INTEL_COMPILER
	mkl_somatcopy(order[0], transA[0], M, N, alpha, A, lda, B, ldb);
#else
	int corder = (order[0] == 'r' || order[0] == 'R') ? CblasRowMajor : CblasColMajor;
	int ctrans = (transA[0] == 't' || transA[0] == 'T') ? CblasTrans : CblasNoTrans;
	cblas_somatcopy(corder, ctrans, M, N, alpha, A, lda, B, ldb);
#endif

	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
	(*env)->ReleaseStringUTFChars(env, j_transA, transA);
	(*env)->ReleaseStringUTFChars(env, j_order, order);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_iomatcopy
(JNIEnv * env, jobject calling_obj, jstring j_order, jstring j_transA, jint M, jint N,
 jintArray j_A, jint lda, jintArray j_B, jint ldb) {
	char * order = (char *)(*env)->GetStringUTFChars(env, j_order, 0);
	char * transA = (char *)(*env)->GetStringUTFChars(env, j_transA, 0);
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);

#ifdef __INTEL_COMPILER
	mkl_somatcopy(order[0], transA[0], M, N, 1.0f, A, lda, B, ldb);
#else
	int corder = (order[0] == 'r' || order[0] == 'R') ? CblasRowMajor : CblasColMajor;
	int ctrans = (transA[0] == 't' || transA[0] == 'T') ? CblasTrans : CblasNoTrans;
	cblas_somatcopy(corder, ctrans, M, N, 1.0f, A, lda, B, ldb);
#endif
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
	(*env)->ReleaseStringUTFChars(env, j_transA, transA);
	(*env)->ReleaseStringUTFChars(env, j_order, order);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_lomatcopy
(JNIEnv * env, jobject calling_obj, jstring j_order, jstring j_transA, jint M, jint N,
 jlongArray j_A, jint lda, jlongArray j_B, jint ldb) {
	char * order = (char *)(*env)->GetStringUTFChars(env, j_order, 0);
	char * transA = (char *)(*env)->GetStringUTFChars(env, j_transA, 0);
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jdouble * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);

#ifdef __INTEL_COMPILER
	mkl_domatcopy(order[0], transA[0], M, N, 1.0, A, lda, B, ldb);
#else
	int corder = (order[0] == 'r' || order[0] == 'R') ? CblasRowMajor : CblasColMajor;
	int ctrans = (transA[0] == 't' || transA[0] == 'T') ? CblasTrans : CblasNoTrans;
	cblas_domatcopy(corder, ctrans, M, N, 1.0, A, lda, B, ldb);
#endif

	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
	(*env)->ReleaseStringUTFChars(env, j_transA, transA);
	(*env)->ReleaseStringUTFChars(env, j_order, order);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_spermute
(JNIEnv * env, jobject calling_obj, jint M, jint N, jint K, jfloatArray j_A, jfloatArray j_B) {
        int i, offset, step = M*N;
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);

#ifdef __INTEL_COMPILER
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          mkl_somatcopy('C', 'T', M, N, 1.0f, A+offset, M, B+offset, N);
        }
#else
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          cblas_somatcopy(CblasColMajor, CblasTrans, M, N, 1.0f, A+offset, M, B+offset, N);
        }
#endif
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_ipermute
(JNIEnv * env, jobject calling_obj, jint M, jint N, jint K, jintArray j_A, jintArray j_B) {
        int i, offset, step = M*N;
	jfloat * A = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE));
	jfloat * B = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE));

#ifdef __INTEL_COMPILER
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          mkl_somatcopy('C', 'T', M, N, 1.0f, A+offset, M, B+offset, N);
        }
#else
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          cblas_somatcopy(CblasColMajor, CblasTrans, M, N, 1.0f, A+offset, M, B+offset, N);
        }
#endif
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_dpermute
(JNIEnv * env, jobject calling_obj, jint M, jint N, jint K, jdoubleArray j_A, jdoubleArray j_B) {
        int i, offset, step = M*N;
	jdouble * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jdouble * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);

#ifdef __INTEL_COMPILER
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          mkl_domatcopy('C', 'T', M, N, 1.0, A+offset, M, B+offset, N);
        }
#else
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          cblas_domatcopy(CblasColMajor, CblasTrans, M, N, 1.0, A+offset, M, B+offset, N);
        }
#endif

	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_lpermute
(JNIEnv * env, jobject calling_obj, jint M, jint N, jint K, jlongArray j_A, jlongArray j_B) {
        int i, offset, step = M*N;
	jdouble * A = (jdouble *)((*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE));
	jdouble * B = (jdouble *)((*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE));

#ifdef __INTEL_COMPILER
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          mkl_domatcopy('C', 'T', M, N, 1.0, A+offset, M, B+offset, N);
        }
#else
        for (i = 0, offset = 0; i < K; i++, offset += step) {
          cblas_domatcopy(CblasColMajor, CblasTrans, M, N, 1.0, A+offset, M, B+offset, N);
        }
#endif

	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_saxpy
(JNIEnv * env, jobject calling_obj, jint N, jfloat a, jfloatArray jX, jint incX, jfloatArray jY, jint incY){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);

        cblas_saxpy(N, a, X, incX, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_saxpyxx
(JNIEnv * env, jobject calling_obj, jint N, jfloat a, jfloatArray jX, jint startX, jfloatArray jY, jint startY){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);

        cblas_saxpy(N, a, X+startX, 1, Y+startY, 1);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_smcscm
(JNIEnv * env, jobject calling_obj, jint M, jint N, jfloatArray j_A, jint lda,
 jfloatArray j_B, jintArray j_ir, jintArray j_jc, jfloatArray j_C, jint ldc){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);
	jint * ir = (*env)->GetPrimitiveArrayCritical(env, j_ir, JNI_FALSE);
	jint * jc = (*env)->GetPrimitiveArrayCritical(env, j_jc, JNI_FALSE);
	jfloat * C = (*env)->GetPrimitiveArrayCritical(env, j_C, JNI_FALSE);

        int ioff = jc[0];
        int i;
#pragma omp parallel for
        for (i = 0; i < N; i++) {
          int j, ir0;
          for (j = jc[i]-ioff; j < jc[i+1]-ioff; j++) {
            ir0 = ir[j]-ioff;
            cblas_saxpy(M, B[j], A+(ir0*lda), 1, C+(i*ldc), 1);
          }
        }

	(*env)->ReleasePrimitiveArrayCritical(env, j_C, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_jc, jc, 0);
        (*env)->ReleasePrimitiveArrayCritical(env, j_ir, ir, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_smcsrm
(JNIEnv * env, jobject calling_obj, jint M, jint N, jfloatArray j_A, jint lda,
 jfloatArray j_B, jintArray j_ir, jintArray j_jc, jfloatArray j_C, jint ldc){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, j_A, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, j_B, JNI_FALSE);
	jint * ir = (*env)->GetPrimitiveArrayCritical(env, j_ir, JNI_FALSE);
	jint * jc = (*env)->GetPrimitiveArrayCritical(env, j_jc, JNI_FALSE);
	jfloat * C = (*env)->GetPrimitiveArrayCritical(env, j_C, JNI_FALSE);

        int ioff = jc[0];
        int i;
#pragma omp parallel for
        for (i = 0; i < N; i++) {
          int j, jj, k;
          for (j = jc[i]-ioff; j < jc[i+1]-ioff; j++) {
            jj = ir[j]-ioff;
            if (M == 1) {
              C[jj*ldc] += B[j] * A[i*lda];
            } else if (M > 10) {
              cblas_saxpy(M, B[j], A+(i*lda), 1, C+(jj*ldc), 1);
            } else {
              int iia = i*lda;
              int jjc = jj*ldc;
              float Bj = B[j];
              for (k = 0; k < M; k++) {
                C[jjc+k] += Bj * A[iia+k];
              }
            }
          }
        }

	(*env)->ReleasePrimitiveArrayCritical(env, j_C, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_jc, jc, 0);
        (*env)->ReleasePrimitiveArrayCritical(env, j_ir, ir, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_B, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, j_A, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_cdot
(JNIEnv * env, jobject calling_obj, jint N, jfloatArray jX, jint incX, jfloatArray jY, jint incY, jfloatArray jZ){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);

    cblas_cdotu_sub(N, X, incX, Y, incY, Z);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_cdotxx
(JNIEnv * env, jobject calling_obj, jint N, jfloatArray jX, jint startX, jfloatArray jY, jint startY, jfloatArray jZ){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);

    cblas_cdotu_sub(N, X+startX, 1, Y+startY, 1, Z);

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_cdotm
(JNIEnv * env, jobject calling_obj, jint nrows, jint ncols, jfloatArray jX, jint ldx, jfloatArray jY, jint ldy, jfloatArray jZ){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);
        int i;

    for (i=0; i<2*ncols; i+=2) {
      cblas_cdotu_sub(nrows, X+i*ldx, 1, Y+i*ldy, 1, Z+i);
    }

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_cdotr
(JNIEnv * env, jobject calling_obj, jint nrows, jint ncols, jfloatArray jX, jint ldx, jfloatArray jY, jint ldy, jfloatArray jZ){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * Z = (*env)->GetPrimitiveArrayCritical(env, jZ, JNI_FALSE);
    int i, j;

    for (i = 0; i < ncols; i++) {
#pragma omp parallel for
      for (j = 0; j < nrows; j++) {
        int ix, iy;
        ix = 2*(j + i*ldx);
        iy = 2*(j + i*ldy);
        Z[2*j] += X[ix] * Y[ix] - X[ix+1] * Y[ix+1];
        Z[2*j+1] += X[ix] * Y[ix+1] + X[ix+1] * Y[ix];
      }
    }

	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jZ, Z, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_cgemv
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint M, jint N, jfloatArray jAlpha,
jfloatArray jA, jint lda, jfloatArray jX, jint incX, jfloatArray jBeta, jfloatArray jY, jint incY){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * alpha = (*env)->GetPrimitiveArrayCritical(env, jAlpha, JNI_FALSE);
	jfloat * beta = (*env)->GetPrimitiveArrayCritical(env, jBeta, JNI_FALSE);

	cblas_cgemv((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, M, N, alpha, A, lda, X, incX, beta, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jBeta, beta, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jAlpha, alpha, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_cgemm
(JNIEnv * env, jobject calling_obj, jint order, jint transA, jint transB, jint M, jint N, jint K,
jfloatArray jAlpha, jfloatArray jA, jint lda, jfloatArray jB, jint ldb, jfloatArray jBeta, jfloatArray jC, jint ldc){
	jfloat * A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
	jfloat * B = (*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE);
	jfloat * C = (*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE);
	jfloat * alpha = (*env)->GetPrimitiveArrayCritical(env, jAlpha, JNI_FALSE);
	jfloat * beta = (*env)->GetPrimitiveArrayCritical(env, jBeta, JNI_FALSE);

	cblas_cgemm((CBLAS_ORDER)order, (CBLAS_TRANSPOSE)transA, (CBLAS_TRANSPOSE)transB, M, N, K,
                    alpha, A, lda, B, ldb, beta, C, ldc);

	(*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_caxpy
(JNIEnv * env, jobject calling_obj, jint N, jfloatArray jA, jfloatArray jX, jint incX,
 jfloatArray jY, jint incY){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * a = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);

     cblas_caxpy(N, a, X, incX, Y, incY);

	(*env)->ReleasePrimitiveArrayCritical(env, jA, a, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_caxpyxx
(JNIEnv * env, jobject calling_obj, jint N, jfloatArray jA, jfloatArray jX, jint startX, jfloatArray jY, jint startY){
	jfloat * X = (*env)->GetPrimitiveArrayCritical(env, jX, JNI_FALSE);
	jfloat * Y = (*env)->GetPrimitiveArrayCritical(env, jY, JNI_FALSE);
	jfloat * a = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);

    cblas_caxpy(N, a, X+startX, 1, Y+startY, 1);

	(*env)->ReleasePrimitiveArrayCritical(env, jA, a, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jY, Y, 0);
	(*env)->ReleasePrimitiveArrayCritical(env, jX, X, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_blockSgemm
(JNIEnv *env, jobject obj, jint transA, jint transB, jint nr, jint nc, jint kk, jfloat alpha, jfloatArray jA, jint aoff, jint lda, jint astep,
 jfloatArray jB, jint boff, jint ldb, jint bstep, jfloat beta, jfloatArray jC, jint coff, jint ldc, jint cstep, jint reps)
{
  int i, at, bt;
  jfloat *A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
  jfloat *B = (*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE);
  jfloat *C = (*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE);

  at = (transA) ? CblasTrans : CblasNoTrans;
  bt = (transB) ? CblasTrans : CblasNoTrans;
  A += aoff;
  B += boff;
  C += coff;
  for (i = 0; i < reps; i++) {
    cblas_sgemm(CblasColMajor, at, bt, nr, nc, kk, alpha, A, lda, B, ldb, beta, C, ldc);
    A += astep;
    B += bstep;
    C += cstep;
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_blockDgemm
(JNIEnv *env, jobject obj, jint transA, jint transB, jint nr, jint nc, jint kk, jdouble alpha, jdoubleArray jA, jint aoff, jint lda, jint astep,
 jdoubleArray jB, jint boff, jint ldb, jint bstep, jdouble beta, jdoubleArray jC, jint coff, jint ldc, jint cstep, jint reps)
{
  int i, at, bt;
  jdouble *A = (*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE);
  jdouble *B = (*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE);
  jdouble *C = (*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE);

  at = (transA) ? CblasTrans : CblasNoTrans;
  bt = (transB) ? CblasTrans : CblasNoTrans;
  A += aoff;
  B += boff;
  C += coff;
  for (i = 0; i < reps; i++) {
    cblas_dgemm(CblasColMajor, at, bt, nr, nc, kk, alpha, A, lda, B, ldb, beta, C, ldc);
    A += astep;
    B += bstep;
    C += cstep;
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_blockSgemm4D
(JNIEnv *env, jobject obj, jint transA, jint transB, jint nr, jint nc, jint kk, jfloat alpha,
 jfloatArray jA, jint aoff, jint lda, jint astep1, jint astep2,
 jfloatArray jB, jint boff, jint ldb, jint bstep1, jint bstep2, jfloat beta,
 jfloatArray jC, jint coff, jint ldc, jint cstep1, jint cstep2, jint reps1, jint reps2)
{
  int i, j, at, bt;
  float *A = (float *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  float *B = (float *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  float *C = (float *)((*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE));
  float *pA, *pB, *pC;

  at = (transA) ? CblasTrans : CblasNoTrans;
  bt = (transB) ? CblasTrans : CblasNoTrans;
  A += aoff;
  B += boff;
  C += coff;
  for (i = 0; i < reps2; i++) {
    for (j = 0; j < reps1; j++) {
      pA = A + (j * astep1 + i * astep2);
      pB = B + (j * bstep1 + i * bstep2);
      pC = C + (j * cstep1 + i * cstep2);
      cblas_sgemm(CblasColMajor, at, bt, nr, nc, kk, alpha, pA, lda, pB, ldb, beta, pC, ldc);
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_blockDgemm4D
(JNIEnv *env, jobject obj, jint transA, jint transB, jint nr, jint nc, jint kk, jdouble alpha,
 jdoubleArray jA, jint aoff, jint lda, jint astep1, jint astep2,
 jdoubleArray jB, jint boff, jint ldb, jint bstep1, jint bstep2, jdouble beta,
 jdoubleArray jC, jint coff, jint ldc, jint cstep1, jint cstep2, jint reps1, jint reps2)
{
  int i, j, at, bt;
  double *A = (double *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  double *B = (double *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  double *C = (double *)((*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE));
  double *pA, *pB, *pC;

  at = (transA) ? CblasTrans : CblasNoTrans;
  bt = (transB) ? CblasTrans : CblasNoTrans;
  A += aoff;
  B += boff;
  C += coff;
  for (i = 0; i < reps2; i++) {
    for (j = 0; j < reps1; j++) {
      pA = A + (j * astep1 + i * astep2);
      pB = B + (j * bstep1 + i * bstep2);
      pC = C + (j * cstep1 + i * cstep2);
      cblas_dgemm(CblasColMajor, at, bt, nr, nc, kk, alpha, pA, lda, pB, ldb, beta, pC, ldc);
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_word2vecFwd
(JNIEnv *env, jobject obj, jint nrows, jint ncols, const jint nwa, const jint nwb, jintArray jWA, jintArray jWB,
 jfloatArray jA, jfloatArray jB, jfloatArray jC)
{
  jint * WA = (jint *)((*env)->GetPrimitiveArrayCritical(env, jWA, JNI_FALSE));
  jint * WB = (jint *)((*env)->GetPrimitiveArrayCritical(env, jWB, JNI_FALSE));
  jfloat * A = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  jfloat * B = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  jfloat * C = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE));
  int i;
#pragma omp parallel for
  for (i = 0; i < ncols; i++) {
    int j, k, c, ia, ib, coff;
    float sum;
    for (j = 0; j < nwa; j++) {
      ia = nrows*WA[j+i*nwa];
      for (k = 0; k < nwb; k++) {
        ib = nrows*WB[k+i*nwb];
        sum = 0;
        for (c = 0; c < nrows; c++) {
          sum += A[c + ia] * B[c + ib];
        }
        coff = nwa * (k + nwb * i);
        C[j + coff] = sum;
      }
    }
  }

  (*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jWB, WB, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jWA, WA, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_word2vecBwd
(JNIEnv *env, jobject obj, jint nrows, jint ncols, jint nwa, jint nwb, jintArray jWA, jintArray jWB,
 jfloatArray jA, jfloatArray jB, jfloatArray jDA, jfloatArray jDB, jfloatArray jC, jfloat lrate)
{
  jint * WA = (jint *)((*env)->GetPrimitiveArrayCritical(env, jWA, JNI_FALSE));
  jint * WB = (jint *)((*env)->GetPrimitiveArrayCritical(env, jWB, JNI_FALSE));
  jfloat * A = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  jfloat * B = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  jfloat * DA = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jDA, JNI_FALSE));
  jfloat * DB = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jDB, JNI_FALSE));
  jfloat * C = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jC, JNI_FALSE));
  int i;

#pragma omp parallel for
  for (i = 0; i < ncols; i++) {
    int j, k, c;
    float cv;
    int ia, ib;
    for (j = 0; j < nwa; j++) {
      ia = nrows*WA[j+i*nwa];
      for (k = 0; k < nwb; k++) {
        ib = nrows*WB[k+i*nwb];
        cv = lrate * C[j + nwa * (k + nwb * i)];
        for (c = 0; c < nrows; c++) {
          A[c + ia] += cv * DB[c + ib];
        }
      }
    }
    for (k = 0; k < nwb; k++) {
      ib = nrows*WB[k+i*nwb];
      for (j = 0; j < nwa; j++) {
        ia = nrows*WA[j+i*nwa];
        cv = lrate * C[j + nwa * (k + nwb * i)];
        for (c = 0; c < nrows; c++) {
          B[c + ib] += cv * DA[c + ia];
        }
      }
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jC, C, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jDB, DB, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jDA, DA, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jWB, WB, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jWA, WA, 0);
}

#define mymax(A, B) ((A>B) ? A : B)

#define mymin(A, B) ((A<B) ? A : B)

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_reduceTensorFloat
(JNIEnv *env, jobject obj, jfloatArray jA, jfloatArray jB, jint m, jint n, jint p, jint op)
{
  jfloat * A = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  jfloat * B = (jfloat *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  int i, j;

  for (i = 0; i < p; i++) {
    int k;
    for (k = 0; k < m; k++) {
      B[k + m * i] = A[k + m * n * i];
    }
  }
  for (j = 1; j < n; j++) {
    switch (op) {
    case 0 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = B[k + m * i] + A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 1 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = B[k + m * i] * A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 2 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymax(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    case 3 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymin(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    default: {}
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_reduceTensorDouble
  (JNIEnv *env, jobject obj, jdoubleArray jA, jdoubleArray jB, jint m, jint n, jint p, jint op)
{
  jdouble * A = (jdouble *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  jdouble * B = (jdouble *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  int i, j;

  for (i = 0; i < p; i++) {
    int k;
    for (k = 0; k < m; k++) {
      B[k + m * i] = A[k + m * n * i];
    }
  }
  for (j = 1; j < n; j++) {
    switch (op) {
    case 0 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] += A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 1 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] *= A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 2 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymax(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    case 3 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymin(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    default: {}
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_reduceTensorInt
(JNIEnv *env, jobject obj, jintArray jA, jintArray jB, jint m, jint n, jint p, jint op)
{
  jint * A = (jint *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  jint * B = (jint *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  int i, j;

  for (i = 0; i < p; i++) {
    int k;
    for (k = 0; k < m; k++) {
      B[k + m * i] = A[k + m * n * i];
    }
  }
  for (j = 1; j < n; j++) {
    switch (op) {
    case 0 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] += A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 1 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] *= A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 2 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymax(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    case 3 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymin(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    default: {}
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}

  JNIEXPORT void JNICALL Java_edu_berkeley_bid_CBLAS_reduceTensorLong
(JNIEnv *env, jobject obj, jlongArray jA, jlongArray jB, jint m, jint n, jint p, jint op)
{
  jlong * A = (jlong *)((*env)->GetPrimitiveArrayCritical(env, jA, JNI_FALSE));
  jlong * B = (jlong *)((*env)->GetPrimitiveArrayCritical(env, jB, JNI_FALSE));
  int i, j;

  for (i = 0; i < p; i++) {
    int k;
    for (k = 0; k < m; k++) {
      B[k + m * i] = A[k + m * n * i];
    }
  }
  for (j = 1; j < n; j++) {
    switch (op) {
    case 0 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] += A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 1 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] *= A[k + m * (j + n * i)];
	}
      }
    }
      break;
    case 2 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymax(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    case 3 : {
#pragma omp parallel for
      for (i = 0; i < p; i++) {
	int k;
	for (k = 0; k < m; k++) {
	  B[k + m * i] = mymin(B[k + m * i], A[k + m * (j + n * i)]);
	}
      }
    }
      break;
    default: {}
    }
  }
  (*env)->ReleasePrimitiveArrayCritical(env, jB, B, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, jA, A, 0);
}


#ifndef CWANNIER_EVCACHE_H
#define CWANNIER_EVCACHE_H

#include <stdbool.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include "input.h"
#include "ecache.h"
#include "submesh.h"

typedef struct {
    int na;
    int nb;
    int nc;
    int num_bands;
    int G_order[3];
    int G_neg[3];
    UEInputFn UEfn;
    gsl_vector **energies;
    gsl_matrix_complex **evecs;
} EvecCache;

EvecCache* init_EvecCache(int na, int nb, int nc, int num_bands, int G_order[3], int G_neg[3], UEInputFn UEfn);

void free_EvecCache(EvecCache *evCache);

void EvecCache_MinMaxVals(EvecCache *evCache, double *emin, double *emax);

EnergyCache* copy_to_Ecache(EvecCache *evCache);

#endif //CWANNIER_EVCACHE_H


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

#include <gsl/gsl_errno.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_const_mksa.h>
#include <gsl/gsl_roots.h>

#include "ccl.h"
#include "ccl_params.h"

// Global variable to hold the neutrino phase-space spline
gsl_spline* nu_spline=NULL;

/* ------- ROUTINE: nu_integrand ------
INPUTS: x: dimensionless momentum, *r: pointer to a dimensionless mass / temperature
TASK: Integrand of phase-space massive neutrino integral
*/
static double nu_integrand(double x, void *r)
{
  double rat=*((double*)(r));
  return sqrt(x*x+rat*rat)/(exp(x)+1.0)*x*x;
}

/* ------- ROUTINE: ccl_calculate_nu_phasespace_spline ------
TASK: Get the spline of the result of the phase-space integral required for massive neutrinos.
*/

gsl_spline* calculate_nu_phasespace_spline(int *status) {
  int N=CCL_NU_MNUT_N;
  double *mnut = ccl_linear_spacing(log(CCL_NU_MNUT_MIN),log(CCL_NU_MNUT_MAX),N);
  double *y=malloc(sizeof(double)*CCL_NU_MNUT_N);
  if (y ==NULL) {
    // Not setting a status_message here because we can't easily pass a cosmology to this function - message printed in ccl_error.c.
    *status = CCL_ERROR_NU_INT;
  }

  // Check whether ccl_splines and ccl_gsl exist; exit gracefully if they
  // can't be loaded
  if(ccl_splines==NULL || ccl_gsl==NULL) ccl_cosmology_read_config();
  if(ccl_splines==NULL || ccl_gsl==NULL) {
    ccl_raise_exception(CCL_ERROR_MISSING_CONFIG_FILE,
                        "ccl_neutrinos.c: Failed to read config file.");
    *status = CCL_ERROR_MISSING_CONFIG_FILE;
    return NULL;
  }

  int stat=0, gslstatus;
  gsl_integration_cquad_workspace * workspace = \
                gsl_integration_cquad_workspace_alloc(ccl_gsl->N_ITERATION);
  gsl_function F;
  F.function = &nu_integrand;
  for (int i=0; i<CCL_NU_MNUT_N; i++) {
    double mnut_=exp(mnut[i]);
    F.params = &(mnut_);
    gslstatus = gsl_integration_cquad(&F, 0, 1000.0,
                                      ccl_gsl->INTEGRATION_NU_EPSABS,
                                      ccl_gsl->INTEGRATION_NU_EPSREL,
                                      workspace,&y[i], NULL, NULL);
    if(gslstatus != GSL_SUCCESS) {
      ccl_raise_gsl_warning(gslstatus, "ccl_neutrinos.c: calculate_nu_phasespace_spline():");
      stat |= gslstatus;
    }
  }
  gsl_integration_cquad_workspace_free(workspace);
  double renorm=1./y[0];
  for (int i=0; i<CCL_NU_MNUT_N; i++)
    y[i]*=renorm;
  gsl_spline* spl = gsl_spline_alloc(A_SPLINE_TYPE, CCL_NU_MNUT_N);
  stat |= gsl_spline_init(spl, mnut, y, CCL_NU_MNUT_N);

  // Check for errors in creating the spline
  if (stat) {
    // Not setting a status_message here because we can't easily pass a cosmology to this function - message printed in ccl_error.c.
    *status = CCL_ERROR_NU_INT;
    free(mnut);
    free(y);
    gsl_spline_free(spl);
  }
  free(mnut);
  free(y);
  return spl;
}

/* ------- ROUTINE: ccl_nu_phasespace_intg ------
INPUTS: accel: pointer to an accelerator which will evaluate the neutrino phasespace spline if defined, mnuOT: the dimensionless mass / temperature of a single massive neutrino
TASK: Get the value of the phase space integral at mnuOT
*/

double nu_phasespace_intg(gsl_interp_accel* accel, double mnuOT, int* status)
{
  // Check if the global variable for the phasespace spline has been defined yet:
  if (nu_spline==NULL) nu_spline =calculate_nu_phasespace_spline(status);
  ccl_check_status_nocosmo(status);

  double integral_value =0.;

  // First check the cases where we are in the limits.
  if (mnuOT<CCL_NU_MNUT_MIN) {
    return 7./8.;
  }
  else if (mnuOT>CCL_NU_MNUT_MAX) {
    return 0.2776566337*mnuOT;
  }

  // Evaluate the spline - this will use the accelerator if it has been defined.
  int gslstatus = gsl_spline_eval_e(nu_spline, log(mnuOT),accel, &integral_value);
  if(gslstatus != GSL_SUCCESS) {
    ccl_raise_gsl_warning(gslstatus, "ccl_neutrinos.c: nu_phasespace_intg():");
    *status |= gslstatus;
  }
  return integral_value*7./8.;
}

/* -------- ROUTINE: Omeganuh2 ---------
INPUTS: a: scale factor, Nnumass: number of massive neutrino species, mnu: total mass in eV of neutrinos, T_CMB: CMB temperature, accel: pointer to an accelerator which will evaluate the neutrino phasespace spline if defined, status: pointer to status integer.
TASK: Compute Omeganu * h^2 as a function of time.
!! To all practical purposes, Neff is simply N_nu_mass !!
*/

double ccl_Omeganuh2 (double a, int N_nu_mass, double* mnu, double T_CMB, gsl_interp_accel* accel, int* status)
{
  double Tnu, a4, prefix_massless, mnuone, OmNuh2;
  double Tnu_eff, mnuOT, intval, prefix_massive;
  double total_mass; // To check if this is the massless or massive case.

  // First check if N_nu_mass is 0
  if (N_nu_mass==0) return 0.0;

  Tnu=T_CMB*pow(4./11.,1./3.);
  a4=a*a*a*a;
  // Check if mnu=0. We assume that in the massless case mnu is a pointer to a single element and that element is 0. This should in principle never be called.
  if (mnu[0] < 0.00017) {  // Limit taken from Lesgourges et al. 2012
    prefix_massless = NU_CONST  * Tnu * Tnu * Tnu * Tnu;
    return N_nu_mass*prefix_massless*7./8./a4;
  }

  // And the remaining massive case.
  // Tnu_eff is used in the massive case because CLASS uses an effective temperature of nonLCDM components to match to mnu / Omeganu =93.14eV. Tnu_eff = T_ncdm * T_CMB = 0.71611 * T_CMB
  Tnu_eff = Tnu * TNCDM / (pow(4./11.,1./3.));

  // Define the prefix using the effective temperature (to get mnu / Omega = 93.14 eV) for the massive case:
  prefix_massive = NU_CONST * Tnu_eff * Tnu_eff * Tnu_eff * Tnu_eff;

  OmNuh2 = 0.; // Initialize to 0 - we add to this for each massive neutrino species.
  for(int i=0; i<N_nu_mass; i++){
	// Get mass over T (mass (eV) / ((kb eV/s/K) Tnu_eff (K))
	// This returns the density normalized so that we get nuh2 at a=0
	mnuOT = mnu[i] / (Tnu_eff/a) * (EV_IN_J / (KBOLTZ));

	// Get the value of the phase-space integral
	intval=nu_phasespace_intg(accel,mnuOT, status);
	OmNuh2 = intval*prefix_massive/a4 + OmNuh2;
  }

  return OmNuh2;
}

/* -------- ROUTINE: Omeganuh2_to_Mnu ---------
INPUTS: OmNuh2: neutrino mass density today Omeganu * h^2, label: how you want to split up the masses, see ccl_neutrinos.h for options, T_CMB: CMB temperature, accel: pointer to an accelerator which will evaluate the neutrino phasespace spline if defined, status: pointer to status integer.
TASK: Given Omeganuh2 today, the method of splitting into masses, and the temperature of the CMB, output a pointer to the array of neutrino masses (may be length 1 if label asks for sum)
*/

double* ccl_nu_masses(double OmNuh2, ccl_neutrino_mass_splits mass_split, double T_CMB,  int* status){

  double sumnu;

  sumnu = 93.14 * OmNuh2;

  // Now split the sum up into three masses depending on the label given:

  if(mass_split==ccl_nu_normal){

     double *mnu;
	 mnu = malloc(3*sizeof(double));

     // See CCL note for how we get these expressions for the neutrino masses in normal and inverted hierarchy.
	 mnu[0] = 2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_pos + 4. * sumnu*sumnu, 0.5)
	         - 0.25 * DELTAM12_sq / (2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_pos + 4. * sumnu*sumnu, 0.5));
	 mnu[1] = 2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_pos + 4. * sumnu*sumnu, 0.5)
	         + 0.25 * DELTAM12_sq / (2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_pos + 4. * sumnu*sumnu, 0.5));
	 mnu[2] = -1./3. * sumnu + 1./3 * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_pos + 4. * sumnu*sumnu, 0.5);
	 if (mnu[0]<0 || mnu[1]<0 || mnu[2]<0){
	    // The user has provided a sum that is below the physical limit.
	    if (sumnu<1e-14){
			mnu[0] = 0.;
			mnu[1] = 0.;
			mnu[2] = 0.;
	    }else{
		 *status = CCL_ERROR_MNU_UNPHYSICAL;
	 }
     }
	 ccl_check_status_nocosmo(status);
	 return mnu;

  } else if (mass_split==ccl_nu_inverted){

	double *mnu;
	mnu = malloc(3*sizeof(double));
	mnu[0] = 2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_neg + 4. * sumnu * sumnu, 0.5)
	         - 0.25 * DELTAM12_sq / (2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_neg + 4. * sumnu * sumnu, 0.5));
	mnu[1] = 2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_neg + 4. * sumnu*sumnu, 0.5)
	         + 0.25 * DELTAM12_sq / (2./3.* sumnu - 1./6. * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_neg + 4. * sumnu*sumnu, 0.5));
	mnu[2] = -1./3. * sumnu + 1./3 * pow(-6. * DELTAM12_sq + 12. * DELTAM13_sq_neg + 4. * sumnu*sumnu, 0.5);
	if(mnu[0]<0 || mnu[1]<0 || mnu[2]<0){
	// The user has provided a sum that is below the physical limit.
	    if (sumnu<1e-14){
			mnu[0] = 0.;
			mnu[1] = 0.;
			mnu[2] = 0.;;
        }else{
		*status = CCL_ERROR_MNU_UNPHYSICAL;
	    }
	}

	ccl_check_status_nocosmo(status);
	return mnu;

  } else if (mass_split==ccl_nu_equal){
	  double *mnu;
	  mnu = malloc(3*sizeof(double));
	  mnu[0] = sumnu/3.;
	  mnu[1] = sumnu/3.;
	  mnu[2] = sumnu/3.;

      return mnu;

  } else if (mass_split == ccl_nu_sum){

	  double *mnu;
	  mnu = malloc(sizeof(double));

      mnu[0] = sumnu;

      return mnu;

  } else{

	  printf("WARNING:  mass option = %d not yet supported\n continuing with normal hierarchy\n", mass_split);
	  double *mnu;
	  mnu = malloc(3*sizeof(double));

	 if (sumnu>0.59){
		 mnu[0] = (sumnu - 0.59) / 3.;
		 mnu[1] = mnu[0] + 0.009;
		 mnu[2] = mnu[0] + 0.05;
	 }else{
		 *status = CCL_ERROR_MNU_UNPHYSICAL;
	 }

	 ccl_check_status_nocosmo(status);
	 return mnu;
  }
}