#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <float.h>
#define COMPEARTH_PRIVATE_CROSS3 1
#define COMPEARTH_PRIVATE_NORM3 1
#define COMPEARTH_PRIVATE_DOT3 1
#define COMPEARTH_PRIVATE_WRAP360 1
#include "compearth.h"
#ifdef COMPEARTH_USE_MKL
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-id-macro"
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#endif
#include <mkl_cblas.h>
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#else
#include <cblas.h>
#endif

//#define MAXMT 64

static void setZero(const int nmt, const double tol, double *__restrict__ X);
static void faultVec2Ang(const double *__restrict__ S,
                         const double *__restrict__ N,
                         double *theta, double *sigma,
                         double *kappa, double *__restrict__ K, int *ierr);
static int pickP1(const double thetaA, const double sigmaA, const double kappaA,
                  const double thetaB, const double sigmaB, const double kappaB,
                  const double tol, const int p1, const int p2);

/*!
 * @brief Converts a moment tensor to six parameters of Tape and Tape 2012.
 *        The reverse program is TT2CMT.c
 *
 * @param[in] nmt           Number of moment tensors.
 * @param[in] Min           [6 x nmt] array of moment tensors in CMT
 *                          convention (i.e. UP-SOUTH-EAST).  M is packed:
 *                          \f$
 *                           M = \{ M_{rr}, M_{\theta \theta}, M_{\phi \phi}
 *                                  M_{r \theta}, M_{r \phi}, M_{\theta \phi} \}
 *                          \f$.
 *                          The leading dimension is 6.
 * @param[in] ldisplay      If true then display the results. \n
 *                          Otherwise, this routine will be quiet unless
 *                          an error is encountered.
 *
 * @param[out] gamma        Angle from DC meridian to MT point.
 *                          Note that \f$ \gamma \in [-30, 30] \f$.
 *                          This is an array of dimension [nmt].
 * @param[out] delta        Angle from deviatoric plane to MT point.
 *                          Note that \f$ \delta \in [-90, 90] \f$.
 *                          This is an array of dimension [nmt].
 * @param[out] M0           Seismic moment in N-m.  This is an array of
 *                          dimension [nmt].
 * @param[out] kappa        Strike angle \f$ \kappa \in [0,360] \f$.
 *                          This is an array of dimension [nmt].
 * @param[out] theta        Dip angle \f$ \theta \in [0,90] \f$.
 *                          This is an array of dimension [nmt].
 * @param[out] sigma        Slip (or rake) angle \f$ \sigma \in [-90,90] \f$.
 *                          This is an array of dimension [nmt].
 * @param[out] K            If K is not NULL then it is the strike vector
 *                          (SOUTH-EAST-UP).  In this case K is an array
 *                          of dimension [3 x nmt] with leading dimension 3.
 * @param[out] N            If N is not NULL then it is the normal vector
 *                          (SOUTH-EAST-UP).  In this case N is an array
 *                          of dimension [3 x nmt] with leading dimension 3.
 * @param[out] S            If S Is not NULL then it is the slip vector
 *                          (SOUTH-EAST-UP).  In this case S is an array
 *                          of dimension [3 x nmt] with leading dimension 3.
 * @param[out] thetadc      If thetadc is not NULL then it is angle between
 *                          the moment tensor and the double couple where
 *                          \f$ \theta_{DC} \in [0,90] \f$.
 *                          In this case thetadc is an array of dimension [nmt].
 * @param[out] lam          If lam is not NULL then these are the eigenvalues
 *                          corresponding to the moment tensor.  In this
 *                          case lam is an array of dimension [3 x nmt]
 *                          with leading dimension 3.
 * @param[out] U            If U is not NULL then this is the basis in
 *                          SOUTH-EAST-UP for the moment tensor.  In this
 *                          case U an array of dimension [3 x 3 x nmt] with
 *                          leading dimension 9 and the understanding that
 *                          each 3 x 3 matrix is in column major format.
 *
 * @result 0 indicates success.
 *
 * @author Carl Tape and translated to C by Ben Baker
 *
 * @date July 2017
 *
 * @copyright MIT
 *
 */
int compearth_CMT2TT(const int nmt, const double *__restrict__ Min,
                     const bool ldisplay,
                     double *__restrict__ gamma,
                     double *__restrict__ delta,
                     double *__restrict__ M0,
                     double *__restrict__ kappa,
                     double *__restrict__ theta,
                     double *__restrict__ sigma,
                     double *__restrict__ K, double *__restrict__ N,
                     double *__restrict__ S, double *__restrict__ thetadc,
                     double *__restrict__ lam, double *__restrict__ U)
{
    double *lamdev, *lamiso, Vwork[9], Yrot[9], M[6],
           Sloc[12], Kloc[12], Nloc[12], kappaL[4], sigmaL[4], thetaL[4];
    double *thetadcWork;
    double Uwork[9*CE_CHUNKSIZE] __attribute__((aligned(64)));
    double lamWork[3*CE_CHUNKSIZE] __attribute__((aligned(64)));
    double Nwork[3*CE_CHUNKSIZE] __attribute__((aligned(64)));
    double Swork[3*CE_CHUNKSIZE] __attribute__((aligned(64)));
    //double lamSpace[3*MAXMT];
    //double uSpace[9*MAXMT];
    bool lwantLam, lwantK, lwantN, lwantS, lwantU;
    int itemp[4], ierr, ierrAll, match, imt, j, jmt, nmtLoc, nMatch;
    const int isort = 1;
    const double rotAngle = 45.0;
    const double tol = 1.e-6;
    ierr = 0;
    if (nmt < 1 || Min == NULL || gamma == NULL || delta == NULL ||
        M0 == NULL || kappa == NULL || theta == NULL || sigma == NULL)
    {
        if (nmt < 1){fprintf(stderr, "%s: No moment tensors\n", __func__);}
        if (Min == NULL){fprintf(stderr, "%s: Min is NULL\n", __func__);}
        if (gamma == NULL){fprintf(stderr, "%s: gamma is NULL\n", __func__);}
        if (delta == NULL){fprintf(stderr, "%s: delta is NULL\n", __func__);}
        if (M0 == NULL){fprintf(stderr, "%s: M0 is NULL\n", __func__);}
        if (kappa == NULL){fprintf(stderr, "%s: kappa is NULL\n", __func__);}
        if (theta == NULL){fprintf(stderr, "%s: theta is NULL\n", __func__);}
        if (sigma == NULL){fprintf(stderr, "%s: sigma is NULL\n", __func__);}
        return -1;
    }
    // Determine if eigenvalue/eigenvector decomposition is requested
    lwantLam = false;
    if (lam != NULL){lwantLam = true;}
    lwantU = false;
    if (U != NULL){lwantU = true;}
    // Determine the desired fault vectors
    lwantK = false;
    lwantN = false;
    lwantS = false;
    if (K != NULL){lwantK = true;}
    if (N != NULL){lwantN = true;}
    if (S != NULL){lwantS = true;}
    // Loop on moment tensor chunks
    for (jmt=0; jmt<nmt; jmt=jmt+CE_CHUNKSIZE)
    {
        nmtLoc = MIN(CE_CHUNKSIZE, nmt - jmt);
        // This is the numerically expensive part b/c of eigendecomposition
        ierrAll = 0;
        for (imt=0; imt<nmtLoc; imt++)
        {
            // KEY: Convert M into another basis.
            // YOU MUST ALSO CHANGE north AND zenith IN fault2vecang BELOW
            // --> U will be with respect to this basis (from CMTdecom.m)
            // ierr = compearth_convertMT(1, CE_USE, CE_NWU, &Min[6*imt], M);
            ierr = compearth_convertMT(1, CE_USE, CE_SEU,
                                       &Min[6*(jmt+imt)], M);
            if (ierr != 0)
            {
                fprintf(stderr, "%s: Error switching basis\n", __func__);
                ierrAll = ierrAll + 1; //break;
            }
            // PART 1: moment tensor source type (or pattern)
            // Decompose moment tensor into eigenvalues + basis (M = U*lam*U')
            // NOTE: ordering of eigenvalues is important.
            ierr = compearth_CMTdecom(1, M, isort, &lamWork[3*imt],
                                      &Uwork[9*imt]);
            if (ierr != 0)
            {
                fprintf(stderr, "%s: Error decomposing CMT\n", __func__);
                ierrAll = ierrAll + 1; //break;
            }
        }
        if (ierrAll != 0)
        {
            fprintf(stderr, "%s: Error during eigendecomposition\n", __func__);
            ierr = 1;
            goto ERROR;
        }
        // Compute the lune coordinates and magnitude from eigenvalues
        lamdev = NULL;
        lamiso = NULL;
        thetadcWork = NULL;
        if (thetadc != NULL){thetadcWork = &thetadc[jmt];}
        ierr = compearth_lam2lune(nmtLoc, lamWork, &gamma[jmt], &delta[jmt],
                                  &M0[jmt], thetadcWork,
                                  lamdev, lamiso);
        // Part 2: moment tensor orientation; TT2012, Section 6.3
        ierr = compearth_eulerUtil_rotmat(1, &rotAngle, 2, Yrot);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error computing rotation matrix\n", __func__);
            return -1;
        }
        // Compute candidate fault vectors
        for (imt=0; imt<nmtLoc; imt++)
        {
            cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans,
                        3, 3, 3, 1.0, &Uwork[9*imt], 3, Yrot, 3,
                        0.0, Vwork, 3); // V = U*Yrot (TT2012, p. 487)
            Swork[3*imt+0] = Vwork[0];
            Swork[3*imt+1] = Vwork[1];
            Swork[3*imt+2] = Vwork[2];
            Nwork[3*imt+0] = Vwork[6];
            Nwork[3*imt+1] = Vwork[7];
            Nwork[3*imt+2] = Vwork[8];
        }
        // Save lambda and U
        if (lwantLam){cblas_dcopy(3*nmtLoc, lamWork, 1, &lam[3*jmt], 1);}
        if (lwantU){cblas_dcopy(9*nmtLoc, Uwork, 1, &U[9*jmt], 1);}
        // Reassign ~0 elements to 0; ~1 elements to 1, and ~-1 elements to -1.
        setZero(nmtLoc, tol, Swork);
        setZero(nmtLoc, tol, Nwork);
        // Compute fault angles for four possible combinations (TT2012 Fig 15)
        for (imt=0; imt<nmtLoc; imt++)
        {
            for (j=0; j<3; j++)
            {
                Sloc[3*0+j] = Swork[3*imt+j]; Nloc[3*0+j] = Nwork[3*imt+j];
                Sloc[3*1+j] =-Swork[3*imt+j]; Nloc[3*1+j] =-Nwork[3*imt+j];
                Sloc[3*2+j] = Nwork[3*imt+j]; Nloc[3*2+j] = Swork[3*imt+j];
                Sloc[3*3+j] =-Nwork[3*imt+j]; Nloc[3*3+j] =-Swork[3*imt+j];
            }
            faultVec2Ang(&Sloc[0], &Nloc[0], &thetaL[0], &sigmaL[0],
                         &kappaL[0], &Kloc[0], &ierr);
            faultVec2Ang(&Sloc[3], &Nloc[3], &thetaL[1], &sigmaL[1],
                         &kappaL[1], &Kloc[3], &ierr);
            faultVec2Ang(&Sloc[6], &Nloc[6], &thetaL[2], &sigmaL[2],
                         &kappaL[2], &Kloc[6], &ierr);
            faultVec2Ang(&Sloc[9], &Nloc[9], &thetaL[3], &sigmaL[3],
                         &kappaL[3], &Kloc[9], &ierr);
            // There are four combinations of N and S that represent a double
            // copule moment tensor, as shown in Figure 15 of TT2012.
            // From these four combinations, there are two possible fault
            // planes.  We want to isoate the combination that is within the
            // boundingregion shown in Figures 16 and B1.
            memset(itemp, 0, 4*sizeof(int));
            nMatch = 0;
            for (j=0; j<4; j++)
            {
                if (thetaL[j] <= 90.0 + tol && fabs(sigmaL[j]) <= 90.0 + tol)
                {
                    itemp[nMatch] = j; //bmatch[j] = true;
                    nMatch = nMatch + 1;
                }
            }
            if (nMatch == 1)
            {
                match = itemp[0];
            }
            else if (nMatch == 2)
            {
                match = pickP1(thetaL[itemp[0]], sigmaL[itemp[0]], kappaL[itemp[0]],
                               thetaL[itemp[1]], sigmaL[itemp[1]], kappaL[itemp[1]],
                               tol, itemp[0], itemp[1]);
                if (match < 0)
                {
                    fprintf(stderr, "%s: Failed to pick a fault plane\n",
                             __func__);
                    ierr = 1;
                    goto ERROR;
                }
            }
            else if (nMatch == 3)
            {
                fprintf(stdout,
                  "%s: Warning mt on bdry of orientation domain 3 candidates\n",
                  __func__);
                fprintf(stdout, "%s: thetas: %e %e %e\n", __func__,
                        thetaL[0], thetaL[1], thetaL[2]);
                fprintf(stdout, "%s: sigmas: %e %e %e\n", __func__,
                        sigmaL[0], sigmaL[1], sigmaL[2]);
                fprintf(stdout, "%s: kappas: %e %e %e\n", __func__,
                        kappaL[0], kappaL[1], kappaL[2]);
                // Just take the first one
                match = itemp[0];
            }
            else if (nMatch == 4)
            {
                fprintf(stderr, "%s: Error not yet programmed\n", __func__);
                ierr = 1;
                goto ERROR;
            }
            else
            {
                fprintf(stderr, "%s: Error no match\n", __func__);
                ierr = 1;
                goto ERROR;
            }
            // Select the angle
            kappa[jmt+imt] = kappaL[match];
            sigma[jmt+imt] = sigmaL[match];
            theta[jmt+imt] = thetaL[match];
            // Fault vectors
            if (lwantK)
            {
                for (j=0; j<3; j++){K[3*(jmt+imt)+j] = Kloc[3*match+j];}
            }
            if (lwantN)
            {
                for (j=0; j<3; j++){N[3*(jmt+imt)+j] = Nloc[3*match+j];}
            }
            if (lwantS)
            {
                for (j=0; j<3; j++){S[3*(jmt+imt)+j] = Sloc[3*match+j];}
            }
        }
    } // Loop on moment tensor chunks
    if (ldisplay)
    {
        fprintf(stderr, "%s: ldisply not yet supported\n", __func__);
    }
ERROR:;
/*
    Uwork = NULL;
    lamWork = NULL;
*/
    return ierr;
}
/*!
 * @brief Returns fault angles in degrees.  Assumes input vectors are in
 *        the South-East-Up basis.
 *
 * @author Carl Tape and converted to C by Ben Baker
 *
 * @copyright MIT
 *
 */
static void faultVec2Ang(const double *__restrict__ S,
                         const double *__restrict__ N,
                         double *theta, double *sigma,
                         double *kappa, double *__restrict__ K, int *ierr)
{
    double v[3], costh, vnorm;
    int ierr1;
    const double deg = 180.0/M_PI;
    // South-East-Up (as In TT2012)
    const double zenith[3] = {0, 0, 1};
    const double negZenith[3] = {0, 0, -1};
    const double north[3] = {-1, 0, 0};
    *ierr = 0;
    *kappa = (double) NAN;
    *theta = (double) NAN;
    *sigma = (double) NAN;
    // Strike vector from TT2012, Eqn 29
    cross3(zenith, N, v);
    vnorm = norm3(v);
    if (vnorm < DBL_EPSILON) //== 0.0)
    {
        fprintf(stderr,
              "%s: Horizontal fault -- strike vector is same as slip vector\n",
                __func__);
        K[0] = S[0];
        K[1] = S[1];
        K[2] = S[2];
    }
    else
    {
        K[0] = v[0]/vnorm;
        K[1] = v[1]/vnorm;
        K[2] = v[2]/vnorm;
    }
    // Figure 14
    *kappa = compearth_eulerUtil_fangleSigned(3, north, K, negZenith, &ierr1);
    if (ierr1 != 0){*ierr = *ierr + 1;}
    *kappa = wrap360(*kappa);
    // Figure 14
    costh = dot3(N, zenith);
    *theta = acos(costh)*deg;
    // Figure 14
    *sigma = compearth_eulerUtil_fangleSigned(3, K, S, N, &ierr1);
    if (ierr1 != 0){*ierr = *ierr + 1;}
    return;
}

static void setZero(const int nmt, const double tol, double *__restrict__ X)
{
   double dmax;
   int i, idmax;
   // Compute the largest element of abs(X)
   idmax = (int) cblas_idamax(3*nmt, X, 1);
   dmax = X[idmax];
   // Elements near zero whilst trying to eliminate round-off errors
   #pragma omp simd
   for (i=0; i<3*nmt; i++)
   {
       if (fabs(X[i]/dmax) < tol){X[i] = 0.0;}
   }
   #pragma omp simd
   for (i=0; i<3*nmt; i++)
   {
       if (fabs(X[i] - 1.0) < tol){X[i] =-1.0;}
   }
   #pragma omp simd
   for (i=0; i<3*nmt; i++)
   {
       if (fabs(X[i] + 1.0) < tol){X[i] = 1.0;}
   }
   return;
}

static int pickP1(const double thetaA, const double sigmaA, const double kappaA,
                  const double thetaB, const double sigmaB, const double kappaB,
                  const double tol, const int p1, const int p2)
{
    int ipick;
    ipick =-1;
    if (fabs(thetaA - 90.0) < tol)
    {
        if (kappaA < 180.0){ipick = p1;}
        if (kappaB < 180.0){ipick = p2;}
        return ipick;
    }
    if (fabs(sigmaA - 90.0) < tol)
    {
        if (kappaA < 180.0){ipick = p1;}
        if (kappaB < 180.0){ipick = p2;}
        return ipick;
    }
    if (fabs(sigmaA + 90.0) < tol)
    {
        if (kappaA < 180.0){ipick = p1;}
        if (kappaB < 180.0){ipick = p2;}
        return ipick;
    }
    fprintf(stderr, "%s: Error no selection criterion was met\n", __func__);
    fprintf(stderr, "thetaA,sigmaA,kappaA=%f,%f,%f\n", thetaA, sigmaA, kappaA);
    fprintf(stderr, "thetaB,sigmaB,kappaB=%f,%f,%f\n", thetaB, sigmaB, kappaB);
    return ipick;
}


/*
 findcluster_ternary_par.c
 written by Eunseok Lee

 function: find the most representative cluster function

 v1: Feb 2, 2018
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/stat.h>
//#include <cem.h>
#include <mpi.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>

void randperm(int*, int);
void load_double_mat(double*, int, int, int, char*);
void load_int_mat(int*, int, int, int, char*);
void load_double_array(double*, int, char*);
void load_int_array(int*, int, char*);
void load_double_value(int,char);
void load_int_value(int,char);
int mat_size_row(int*);
int find_value_in_array(int*,int,int,int);
void mat2d_prod(double*, int, int, double*, int, int, double*, int, int);
double obtainerr2_par(int,int,double*, int, int, int*, int, double*);
void least_square_solver(double*, int, int, double*, double*);

#define MASTER 0
#define FROM_MASTER 1
#define FROM_WORKER 2

int main(int argc, char **argv)
{
    int n1, n2, n3;
    int np, nfu;
    double kv[3][3], pk[3][3], pp[8][3];
    double *rp, *rpL, *rpN, *rpO;
    int *map_to_cluster1, *map_to_cluster2, *map_to_cluster3;
    int *nlist;
    int neighbor_num;
    int ncluster, ncluster2, ncluster3, ncorr_col;
    int c2start, c3start;
    int data;
    double *E, *Ef;
    int ndata;
    int i, j, k, nj, nk, ctn;
    int iter, max_iter;
    double kT=0.0256;   // Default value of kT
    int howmanycluster;
    int dispfreq;
    char buff_line[200], dummy[200], param_name[100];
    int param_name_len;
    double cvs_tol = 0.01;  // Default value of cvs_tol
    int construct_corr_mat = 0; //Default value of construct_corr_mat

    // mpi parameters and initialization
    int numprocs, rank, mtype;
    int row_dist_size;
    int row_ini, row_end, row_offset;
    MPI_Status status;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);  // Get # processors
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);      // Get my rank (id)

    if (numprocs>1)
        printf("Parallel computing: the input files are read by all nodes. Multiple messages are not due to error!\n",numprocs);

    // Construct conrrelation matrix. It can also be loaded.

    int n_corr_mat_ug_row;
    int n_non_singular_col;
    FILE *fp;
    char dirname[100]="dir_inputs";
    char paramfilename[100];
    sprintf(paramfilename,"%s/param.dat",dirname);
    char corr_mat_ugs_filename[200];
    fp = fopen(paramfilename, "r");
    while(fgets(buff_line,sizeof(buff_line),fp) != NULL) {
        if (buff_line[0] == '#') {
            //            printf("Comment line: %s",buff_line);
            continue;
        }
        strcpy(param_name,"corr_mat_ugs_filename");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %s",dummy,corr_mat_ugs_filename);
            if (rank == MASTER)
                printf("corr_mat_ugs_filename = %s\n",corr_mat_ugs_filename);
        }
        strcpy(param_name,"n_corr_mat_ug_row");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %d",dummy,&n_corr_mat_ug_row);
            if (rank == MASTER)
                printf("n_corr_mat_ug_row = %d\n",n_corr_mat_ug_row);
        }
        strcpy(param_name,"n_non_singular_col");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %d",dummy,&n_non_singular_col);
            if (rank == MASTER)
                printf("n_non_singular_col = %d\n",n_non_singular_col);
        }
        strcpy(param_name,"howmanycluster");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %d",dummy,&howmanycluster);
            if (rank == MASTER)
                printf("howmanycluster = %d\n",howmanycluster);
        }
        strcpy(param_name,"max_iter");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %d",dummy,&max_iter);
            if (rank == MASTER)
                printf("max_iter = %d\n",max_iter);
        }
        strcpy(param_name,"kT");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %lf",dummy,&kT);
            if (rank == MASTER)
                printf("kT = %f\n",kT);
        }
        strcpy(param_name,"cvs_tol");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %lf",dummy,&cvs_tol);
            if (rank == MASTER)
                printf("cvs_tol = %f\n",cvs_tol);
        }
        strcpy(param_name,"dispfreq");
        param_name_len = strlen(param_name);
        strncpy(dummy,buff_line,param_name_len);
        dummy[param_name_len] = '\0';
        if (strcmp(dummy,param_name)==0) {
            sscanf(buff_line,"%s %d",dummy,&dispfreq);
            if (rank == MASTER)
                printf("dispfreq = %d\n",dispfreq);
        }
    }
    fclose(fp);

    char loadfilename[100];
    double *corr_mat_ugs;
    corr_mat_ugs = (double*) malloc(n_corr_mat_ug_row*n_non_singular_col*sizeof(double));
    sprintf(loadfilename,"%s/%s",dirname,corr_mat_ugs_filename);
    load_double_mat(corr_mat_ugs,n_corr_mat_ug_row,n_non_singular_col,1,loadfilename);

    int usefulcorr_col[n_non_singular_col];
    sprintf(loadfilename,"%s/usefulcorr_col.dat",dirname);
    load_int_array(usefulcorr_col,n_non_singular_col,loadfilename);

    double Ef_ug[n_corr_mat_ug_row];
    sprintf(loadfilename,"%s/Ef_ug.dat",dirname);
    load_double_array(Ef_ug,n_corr_mat_ug_row,loadfilename);

/*    int nC_ug[n_corr_mat_ug_row];
    sprintf(loadfilename,"%s/nC_ug.dat",dirname);
    load_int_array(nC_ug,n_corr_mat_ug_row,loadfilename);
*/
    row_offset = (int) ceil(n_corr_mat_ug_row*1.0/numprocs);
    if (numprocs>1 && rank==MASTER)
        printf("Correlation Matrix is calculated over %d processors: row_offset = %d\n",numprocs,row_offset);
    double err2_sum_from_master;
    double err2_sum_from_worker;
    int cluster_set1[howmanycluster];

    /**** Start of MC simulation ****/
    if (rank == MASTER) {
        int cluster_set1_old[howmanycluster], cluster_set1_min[howmanycluster], cluster_set2[n_non_singular_col-howmanycluster];
        double cvs, cvs_old, cvs_min;
        int select1, select2, target, candidate;

        for (i=0;i<howmanycluster;i++)
            cluster_set1[i] = i;
        for (i=0;i<n_non_singular_col-howmanycluster;i++)
            cluster_set2[i] = i+howmanycluster;

        mtype = FROM_MASTER;
        for (i=1;i<numprocs;i++) {
            MPI_Send(&cluster_set1, howmanycluster, MPI_INT, i, mtype, MPI_COMM_WORLD);
        }
        row_ini = rank * row_offset;
        row_end = row_ini +row_offset;
        err2_sum_from_master = obtainerr2_par(row_ini,row_end,corr_mat_ugs,n_corr_mat_ug_row,n_non_singular_col,cluster_set1,howmanycluster,Ef_ug);
//        printf("initial err2 = %f\n",err2_sum_from_master);
        for (i=1;i<numprocs;i++) {
            mtype = FROM_WORKER;
            MPI_Recv(&err2_sum_from_worker, 1, MPI_DOUBLE, i, mtype, MPI_COMM_WORLD, &status);
            err2_sum_from_master += err2_sum_from_worker;
        }
        printf("initial err2 = %f\n",err2_sum_from_master);
        cvs = sqrt(err2_sum_from_master/n_corr_mat_ug_row);
        cvs_min = cvs;
        for (i=0;i<howmanycluster;i++)
            cluster_set1_min[i] = cluster_set1[i];
        printf("initial cvs = %f\n",cvs);
        for (iter=0;iter<max_iter;iter++) {
            select1 = (int) floor(drand48()*howmanycluster);
            select2 = (int) floor(drand48()*(n_non_singular_col-howmanycluster));
            target = cluster_set1[select1];
            candidate = cluster_set2[select2];
            cvs_old = cvs;
            cluster_set1[select1] = candidate;
            mtype = FROM_MASTER;
            for (i=1;i<numprocs;i++) {
                MPI_Send(&cluster_set1, howmanycluster, MPI_INT, i, mtype, MPI_COMM_WORLD);
            }
            row_ini = rank * row_offset;
            row_end = row_ini +row_offset;
            err2_sum_from_master = obtainerr2_par(row_ini,row_end,corr_mat_ugs,n_corr_mat_ug_row,n_non_singular_col,cluster_set1,howmanycluster,Ef_ug);
            for (i=1;i<numprocs;i++) {
                mtype = FROM_WORKER;
                MPI_Recv(&err2_sum_from_worker, 1, MPI_DOUBLE, i, mtype, MPI_COMM_WORLD, &status);
                err2_sum_from_master += err2_sum_from_worker;
            }
            cvs = sqrt(err2_sum_from_master/n_corr_mat_ug_row);
            if (cvs < cvs_old)
                cluster_set2[select2] = target;
            else if (exp(-(cvs-cvs_old)/kT) > drand48())
                cluster_set2[select2] = target;
            else {
                cluster_set1[select1] = target;
                cvs = cvs_old;
            }
            if (cvs < cvs_min) {
                for (i=0;i<howmanycluster;i++)
                    cluster_set1_min[i] = cluster_set1[i];
                cvs_min = cvs;
            }
            if (cvs < cvs_tol)
                break;
            if (iter%dispfreq==0)
                printf("iter=%8d, cvs=%f, cvs_min=%f, cvs_min/fu=%f\n",iter,cvs,cvs_min,cvs_min/72);
        }

        // print the result
        printf("End of MC iteration. The selected clusters are...\n");
        printf("ID in non-singular | ID in entire\n");
        for (i=0;i<howmanycluster;i++)
            printf("    %4d                %4d\n",cluster_set1_min[i],usefulcorr_col[cluster_set1_min[i]]);

        double err_pred, rms;
        double Ef_pred[n_corr_mat_ug_row];
        double *corr_mat_ugs_reduced;
        corr_mat_ugs_reduced = (double*) malloc(n_corr_mat_ug_row*howmanycluster*sizeof(double));
        double eci[howmanycluster];
        for (i=0;i<n_corr_mat_ug_row;i++) {
            for (j=0;j<howmanycluster;j++)
                *(corr_mat_ugs_reduced+howmanycluster*i+j) = *(corr_mat_ugs+n_non_singular_col*i+cluster_set1_min[j]);
        }
        least_square_solver(corr_mat_ugs_reduced,n_corr_mat_ug_row,howmanycluster,Ef_ug,eci);
        mat2d_prod(Ef_pred,n_corr_mat_ug_row,1,corr_mat_ugs_reduced,n_corr_mat_ug_row,howmanycluster,eci,howmanycluster,1);
        err_pred = 0.0;
        for (i=0;i<n_corr_mat_ug_row;i++)
            err_pred += (Ef_ug[i] - Ef_pred[i])*(Ef_ug[i] - Ef_pred[i]);
        rms = sqrt(err_pred/n_corr_mat_ug_row);

        // Write result into files
        FILE *fp2, *fp3;
        char cluster_set_filename[200];
        char eci_filename[100];
        strcpy(dirname,"dir_result");
        struct stat st = {0};
        if (stat(dirname, &st) == -1) {
            mkdir(dirname,0777);
            printf("Created a new directory for storing result.\n");
        }
        sprintf(cluster_set_filename, "%s/cluster_set_N%d.dat",dirname,howmanycluster);
        sprintf(eci_filename,"%s/x.dat",dirname);
        fp2 = fopen(cluster_set_filename,"w");
        fp3 = fopen(eci_filename,"w");
        for (i=0;i<howmanycluster;i++) {
            fprintf(fp2,"%4d\t%4d\n",cluster_set1_min[i],usefulcorr_col[cluster_set1_min[i]]);
            fprintf(fp3,"%.4f\n",eci[i]);
        }
    }
    else {
        for (iter=0;iter<(max_iter+1);iter++) {
            mtype = FROM_MASTER;
            MPI_Recv(&cluster_set1, howmanycluster, MPI_INT, MASTER, mtype, MPI_COMM_WORLD, &status);
            row_ini = rank * row_offset;
            row_end = row_ini + row_offset;
            if (rank == numprocs-1)
                row_end = n_corr_mat_ug_row;  // last processor till the last row, -1 is taken inside obtainerr2_par
            err2_sum_from_worker = obtainerr2_par(row_ini,row_end,corr_mat_ugs,n_corr_mat_ug_row,n_non_singular_col,cluster_set1,howmanycluster,Ef_ug);
            mtype = FROM_WORKER;
            MPI_Send(&err2_sum_from_worker, 1, MPI_DOUBLE, MASTER, mtype, MPI_COMM_WORLD);
        }
    }
    MPI_Finalize();

}

void load_double_mat(double *A, int Arow, int Acol, int Apgs, char *datfilename) {

    FILE *fp;
    int i, j, k;
    double tmp;

    printf("filename: %s\n", datfilename);
    fp = fopen(datfilename, "r");
    for (k=0;k<Apgs;k++)
        for (i=0;i<Arow;i++)
            for (j=0;j<Acol;j++) {
                fscanf(fp, "%lf", &tmp);
                *(A+Arow*Acol*k+Acol*i+j) = tmp;
            }

    fclose(fp);

}

void load_int_mat(int *A, int Arow, int Acol, int Apgs, char *datfilename) {

    FILE *fp;
    int i, j, k, tmp;

    printf("filename: %s\n", datfilename);
    fp = fopen(datfilename, "r");
    for (k=0;k<Apgs;k++)
        for (i=0;i<Arow;i++)
            for (j=0;j<Acol;j++) {
                fscanf(fp, "%d", &tmp);
                *(A+Arow*Acol*k+Acol*i+j) = tmp;
            }

    fclose(fp);

}

void load_int_array(int *array, int arraysize, char *datfilename) {

    FILE *fp;
    int i;

    printf("filename: %s\n", datfilename);
    fp = fopen(datfilename, "r");
    //    printf("step2\n");
    for (i=0;i<arraysize;i++) {
        fscanf(fp, "%d", &array[i]);
        //        printf("%d\n",array[i]);
    }

    fclose(fp);
    //    printf("step3\n");
}

void load_double_array(double *array, int arraysize, char *datfilename) {

    FILE *fp;
    int i;

    printf("filename: %s\n", datfilename);
    fp = fopen(datfilename, "r");
    //    printf("step4\n");
    for (i=0;i<arraysize;i++) {
        fscanf(fp, "%lf", &array[i]);
        //        printf("%f\n",array[i]);
    }

    fclose(fp);
    //    printf("step5\n");
}

void randperm(int *a, int np) {

    int i, id1, id2, id3, tmpr, num_perm;

    num_perm = 100*np;

    for (i=0;i<np;i++)
        *(a+i) = i;

    for (i=0;i<num_perm;i++) {
        id1 = rand() % np;
        id3 = rand() % np;
        id2 = (id1 + id3) % np;
        if (id1 != id2) {
            tmpr = *(a+id2);
            *(a+id2) = *(a+id1);
            *(a+id1) = tmpr;
        }
    }
}

int find_value_in_array(int *A, int target_rowA, int colA, int target_value) {
    int i;

    for (i=0;i<colA;i++)
        if (*(A+colA*target_rowA+i)==target_value)
            return(i);
    if (i==colA)
        return(-1);
}


#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <gbpLib.h>
#include <gbpRNG.h>
#include <gbpMCMC.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_fit.h>
#include <gsl/gsl_interp.h>

void generate_MCMC_parameters(MCMC_info *MCMC) {
    int                i_P;
    int                j_P;
    double             factor_i;
    int                flag_continue;
    static int         reflect_priors;
    static int         n_P;
    static double      factor;
    static double *    P_new;
    static double *    P_chain;
    static double *    P_init;
    static double *    P_limit_min;
    static double *    P_limit_max;
    static gsl_vector *b;
    static gsl_matrix *m;
    static RNG_info *  RNG;

    // Initialize a few things on the first call
    switch(MCMC->first_parameter_call) {
        case GBP_TRUE:
            n_P                        = MCMC->n_P;
            P_new                      = MCMC->P_new;
            P_chain                    = MCMC->P_chain;
            P_init                     = MCMC->P_init;
            P_limit_min                = MCMC->P_limit_min;
            P_limit_max                = MCMC->P_limit_max;
            b                          = MCMC->b;
            m                          = MCMC->m;
            RNG                        = MCMC->RNG;
            factor                     = 2.4 / sqrt((double)MCMC->n_M_total);
            MCMC->first_parameter_call = GBP_FALSE;
            reflect_priors             = SID_CHECK_BITFIELD_SWITCH(MCMC->mode, MCMC_MODE_REFLECT_PRIORS);
            break;
    }

    // Loop until a satisfactory set of parameters has been selected
    flag_continue = GBP_TRUE;
    while(flag_continue) {
        // Generate a random displacement vector
        factor_i = MCMC->temperature * factor;
        for(i_P = 0; i_P < n_P; i_P++)
            gsl_vector_set(b, i_P, factor_i * random_gaussian(RNG));

        // Use the rotated covariance matrix (if available)
        if(m != NULL) {
            memcpy(P_new, P_chain, n_P * sizeof(double));
            for(i_P = 0; i_P < n_P; i_P++) {
                for(j_P = 0; j_P < n_P; j_P++)
                    P_new[i_P] += gsl_matrix_get(m, i_P, j_P) * gsl_vector_get(b, j_P);
            }
        } else {
            for(j_P = 0; j_P < n_P; j_P++)
                P_new[j_P] = P_init[j_P] * (1. + gsl_vector_get(b, j_P));
        }

        // Enforce parameter limits
        if(reflect_priors) {
            for(i_P = 0; i_P < n_P; i_P++) {
                if(P_new[i_P] < P_limit_min[i_P])
                    P_new[i_P] = 2 * (P_limit_min[i_P]) - P_new[i_P];
                if(P_new[i_P] > P_limit_max[i_P])
                    P_new[i_P] = 2 * (P_limit_max[i_P]) - P_new[i_P];
            }
        } else {
            for(i_P = 0, flag_continue = GBP_FALSE; i_P < n_P; i_P++)
                if(P_new[i_P] < P_limit_min[i_P] || P_new[i_P] > P_limit_max[i_P])
                    flag_continue = GBP_TRUE;
        }
    }

    if(!SID_CHECK_BITFIELD_SWITCH(MCMC->mode, MCMC_MODE_PARALLEL))
        SID_Bcast(P_new, n_P, SID_DOUBLE, SID_MASTER_RANK, MCMC->comm);
}


/**
 * @file bblas_daccuracy.c
 *
 * @brief BBLAS accuracy tests for double_Complex precision.
 *
 *  BBLAS is a software package provided by Univ. of Manchester,
 *  Univ. of Tennessee.
 *
 * @version 1.0.0
 * @author  Samuel  D. Relton
 * @author  Pedro   V. Lara
 * @author  Mawussi Zounon
 * @date    2016-02-20
 *
 **/

#ifndef DOXYGEN_SHOULD_SKIP_THIS
/**
 * Code generation
 * @generated from bblas_zaccuracy.c normal z -> d, Mon Jun  6 09:44:14 2016
 **/
#endif

#include "bblas_common.h"
#include <lapacke.h>
#include <cblas.h>

#define REAL

/**
 * bblas_dgemm_tolerance computes the forward error
 * @f[\|C - C_{\mathrm{seq}}\|_{\infty}/((K|\alpha|\|A\|_{\infty}\|B\|_{\infty} +
 *           |\beta|\|C_{\mathrm{init}}\|_{\infty})\epsilon).@f]
 *
 **/

void bblas_dgemm_tolerance(bblas_dtest_t *test, double **C_final)
{

    /*Local variables */
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int Am, An, Bm, Bn, M, K, N, ldc;
    int max_work_size = max(test->maxK,max(test->maxM, test->maxN));
    double Anorm, Bnorm, Rnorm, result;
    double eps = LAPACKE_dlamch_work('e');
    double alpha =-1;
    double *work = (double *)malloc(max_work_size*sizeof(double));

    /*Temporary buffer to save (test-arrayC -C_final) */
    double **C_diff;
    C_diff = (double **) malloc(batch_count*sizeof(double *));

    /*Make a copy of test->arrayC in C_diff */
    bblas_dcopy_Cinit(test, C_diff);

    /*Variable size */
    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    M = test->M[batch_iter];
	    K   = test->K[batch_iter];
	    N   = test->N[batch_iter];
	    ldc = test->ldc[batch_iter];

	    if (test->transA[batch_iter] == BblasNoTrans) {
		Am = M; An = K;
	    } else {
		Am = K; An = M;
	    }
	    if (test->transB[batch_iter] == BblasNoTrans) {
		Bm = K; Bn = N;
	    } else {
		Bm = N; Bn = K;
	    }

	    /*Compute the error C - C_finial */
	    cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

	    /*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', M, N, C_diff[batch_iter], ldc, work);

	    /*Compute the infinity norm of A */
	    Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, An, test->arrayA[batch_iter], Am, work);

	    /*Compute the infinity norm of B */
	    Bnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Bm, Bn, test->arrayB[batch_iter], Bm, work);

	    result = Rnorm / ((K*cabs(test->alpha[batch_iter])*Anorm*Bnorm + cabs(test->beta[batch_iter])*test->Cinitnorm[batch_iter])*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}

    }else  if( test->batch_opts == BBLAS_FIXED ) //fixed  size
    {
	M   = test->M[first_index];
	K   = test->K[first_index];
	ldc = test->ldc[first_index];
	N   = test->N[first_index];

	if (test->transA[first_index] == BblasNoTrans) {
	    Am = M; An = K;
	} else {
	    Am = K; An = M;
	}
	if (test->transB[first_index] == BblasNoTrans) {
	    Bm = K; Bn = N;
	} else {
	    Bm = N; Bn = K;
	}

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
		/*Compute the error C - C_finial */
	    cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);
	    /*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', M, N, C_diff[batch_iter], ldc, work);

	    /*Compute the infinity norm of A */
	    Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, An, test->arrayA[batch_iter], Am, work);

	    /*Compute the infinity norm of B */
	    Bnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Bm, Bn, test->arrayB[batch_iter], Bm, work);

	    /*Compute the relative error */
	    result = Rnorm / ((K*cabs(test->alpha[first_index])*Anorm*Bnorm + cabs(test->beta[first_index])*test->Cinitnorm[batch_iter])*eps);

	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal: Fixed, Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}
    }else
    {
	bblas_error("bblas_dtesting.c", "wrong batch_opts value");
    }

/*Free allocated memory */
    for(int index =0; index < batch_count; index++)
    {
	free(C_diff[index]);
    }
    free(C_diff);
    free(work);
}

/**
 * bblas_dstarmm_tolerance computes the forward error
 * @f[\|C - C_{\mathrm{seq}}\|_{\infty}/((K|\alpha|\|A\|_{\infty}\|B\|_{\infty} +
 * |\beta|\|C_{\mathrm{init}}\|_{\infty})\epsilon).@f]
 *
 **/

void bblas_dstarmm_tolerance(bblas_dtest_t *test, double **C_final)
{
    /*Local variables */
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int Am, Bm, Bn, N, ldc;
    int max_work_size = max(test->maxM, test->maxN);
    double Anorm, Bnorm, Rnorm, result;
    double eps = LAPACKE_dlamch_work('e');
    double alpha =-1;
    double *work = (double *)malloc(max_work_size*sizeof(double));

    /*Temporary buffer to save (test-arrayC -C_final) */
    double **C_diff;
    C_diff = (double **) malloc(batch_count*sizeof(double *));

    /*Make a copy of test->arrayC in C_diff */
    bblas_dcopy_Cinit(test, C_diff);

    /*Variable size */
    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    N = test->N[batch_iter];
	    ldc = test->ldc[batch_iter];

	    if(test->side[batch_iter] == BblasLeft )
	    {
		Am = test->M[batch_iter];
	    } else {
		Am = test->N[batch_iter];
	    }
	    Bm = test->ldb[batch_iter];
	    Bn = test->N[batch_iter];
	    /*Compute the error C - C_finial */
	    cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

		/*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldc, N, C_diff[batch_iter], ldc, work);

	    /*Compute the infinity norm of A */
	    Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, Am, test->arrayA[batch_iter], Am, work);

	    /*Compute the infinity norm of B */
	    Bnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Bm, Bn, test->arrayB[batch_iter], Bm, work);

	    result = Rnorm / ((N*cabs(test->alpha[batch_iter])*Anorm*Bnorm + cabs(test->beta[batch_iter])*test->Cinitnorm[batch_iter])*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}

    }else  if( test->batch_opts == BBLAS_FIXED ) //fixed  size
    {
	N = test->N[first_index];
	ldc = test->ldc[first_index];

	if(test->side[first_index] == BblasLeft )
	{
	    Am = test->M[first_index];
	} else {
	    Am = test->N[first_index];
	}
	Bm = test->ldb[first_index];
	Bn = test->N[first_index];

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    /*Compute the error C - C_finial */
	    cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

	    /*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldc, N, C_diff[batch_iter], ldc, work);

	    /*Compute the infinity norm of A */
	    Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, Am, test->arrayA[batch_iter], Am, work);

	    /*Compute the infinity norm of B */
	    Bnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Bm, Bn, test->arrayB[batch_iter], Bm, work);

	    /*Compute the relative error */
	    result = Rnorm / ((N*cabs(test->alpha[first_index])*Anorm*Bnorm + cabs(test->beta[first_index])*test->Cinitnorm[batch_iter])*eps);

	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal: Fixed, Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}
    }else
    {
	bblas_error("bblas_dtesting.c", "wrong batch_opts value");
    }

/*Free allocated memory */
    for(int index =0; index < batch_count; index++)
    {
	free(C_diff[index]);
    }
    free(C_diff);
    free(work);
}

/**
 * bblas_dstark_tolerance computes the forward error
 * @f[\|C - C_{\mathrm{seq}}\|_{\infty}/((K|\alpha|\|A\|_{\infty}\|A\|_1 +
 * |\beta|\|C_{\mathrm{init}}\|_{\infty})\epsilon).@f]
 *
 **/

void bblas_dstark_tolerance(bblas_dtest_t *test, double **C_final)
{
    /*Local variables */
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int Am, An, N, K, ldc;
    char u;
    int max_work_size = max(test->maxK, test->maxN);
    double Anorm, ATnorm, Rnorm, result;
    double eps = LAPACKE_dlamch_work('e');
    double alpha_norm, beta_norm;
    double alpha =-1;
    double *work = (double *)malloc(max_work_size*sizeof(double));

    /*Temporary buffer to save (test-arrayC -C_final) */
    double **C_diff;
    C_diff = (double **) malloc(batch_count*sizeof(double *));

    /*Make a copy of test->arrayC in C_diff */
    bblas_dcopy_Cinit(test, C_diff);

    /*Variable size */
    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    N = test->N[batch_iter];
	    K = test->K[batch_iter];
	    ldc = test->ldc[batch_iter];

	    if (test->trans[batch_iter] == BblasNoTrans) {
		Am = N; An = K;
	    } else {
		Am = K; An = N;
	    }

	    if(test->uplo[batch_iter] == BblasLower )
	      {
		u = 'L';
	      }else
	      {
		u = 'U';
	      }
	    /*Compute the error C - C_finial */
	    cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

	    /*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlansy_work(LAPACK_COL_MAJOR, 'I', u, N, C_diff[batch_iter], ldc, work);

	    /*Compute the infinity norm of A */
	    Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, An, test->arrayA[batch_iter], Am, work);

	    /*Compute the one norm of A */
	    ATnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'O', Am, An, test->arrayA[batch_iter], Am, work);

	    if (test->routine == BBLAS_HERK)
	    {
		beta_norm   = cabs(test->beta_herk[batch_iter]);
		alpha_norm  = cabs(test->alpha_herk[batch_iter]);
	    }else{
		beta_norm  = cabs(test->beta[batch_iter]);
		alpha_norm  = cabs(test->alpha[batch_iter]);
	    }

	    result = Rnorm / ((K*alpha_norm*Anorm*ATnorm + beta_norm*test->Cinitnorm[batch_iter])*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}

    }else  if( test->batch_opts == BBLAS_FIXED ) //fixed  size
    {
	    N = test->N[first_index];
	    K = test->K[first_index];
	    ldc = test->ldc[first_index];

	    if (test->trans[first_index] == BblasNoTrans) {
		Am = N; An = K;
	    } else {
		Am = K; An = N;
	    }
	    if(test->uplo[first_index] == BblasLower )
	      {
		u = 'L';
	      }else
	      {
		u = 'U';
	      }

	    for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	      {
		/*Compute the error C - C_finial */
		cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

		Rnorm = LAPACKE_dlansy_work(LAPACK_COL_MAJOR, 'I', u, N, C_diff[batch_iter], ldc, work);

		/*Compute the infinity norm of A */
		Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, An, test->arrayA[batch_iter], Am, work);

		/*Compute the one norm of A */
		ATnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'O', Am, An, test->arrayA[batch_iter], Am, work);

		if (test->routine == BBLAS_HERK)
		{
		    beta_norm   = cabs(test->beta_herk[first_index]);
		    alpha_norm  = cabs(test->alpha_herk[first_index]);
		}else{
		    beta_norm   = cabs(test->beta[first_index]);
		    alpha_norm  = cabs(test->alpha[first_index]);
		}

		/*Compute the relative error */
		result = Rnorm / ((K*alpha_norm*Anorm*ATnorm +
				   beta_norm*test->Cinitnorm[batch_iter])*eps);

		switch(test->target)
		{
		    case BBLAS_MKL:
			test->mkl_error[batch_iter] = result;
			break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		  test->device_error[batch_iter] = result;
		  break;

		    case BBLAS_OTHER:
			test->other_error[batch_iter] = result;
			break;
		    default:
			printf("In bblas_dcheck_Cfinal: Fixed, Target no defined\n");
			exit(EXIT_FAILURE);
		}
	    }
    }else
    {
	bblas_error("bblas_dtesting.c", "wrong batch_opts value");
    }

/*Free allocated memory */
    for(int index =0; index < batch_count; index++)
    {
	free(C_diff[index]);
    }
    free(C_diff);
    free(work);
}

/**
 * bblas_dstar2k_tolerance computes the forward error
 * @f[\|C - C_{\mathrm{seq}}\|_{\infty}/((K|\alpha|\|A\|_{\infty}\|B\|_1 +
 * K|\alpha|\|B\|_{\infty}\|A\|_1  +
 * |\beta|\|C_{\mathrm{init}}\|_{\infty})\epsilon).@f]
 *
 **/

void bblas_dstar2k_tolerance(bblas_dtest_t *test, double **C_final)
{
    /*Local variables */
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int Am, An, Bn, Bm, N, K, ldc;
    int max_work_size = max(test->maxK, test->maxN);
    double Anorm, Bnorm, ATnorm, BTnorm,  Rnorm, result;
    double beta_norm;
    double eps = LAPACKE_dlamch_work('e');
    double alpha =-1;
    double *work = (double *)malloc(max_work_size*sizeof(double));

    /*Temporary buffer to save (test-arrayC -C_final) */
    double **C_diff;
    C_diff = (double **) malloc(batch_count*sizeof(double *));

    /*Make a copy of test->arrayC in C_diff */
    bblas_dcopy_Cinit(test, C_diff);

    /*Variable size */
    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    N = test->N[batch_iter];
	    K = test->K[batch_iter];
	    ldc = test->ldc[batch_iter];

	    if (test->trans[batch_iter] == BblasNoTrans) {
		Am = N; An = K;
		Bn = K;
	    } else {
		Am = K; An = N;
		Bn = N;
	    }
	    Bm = test->ldb[batch_iter];

	    /*Compute the error C - C_finial */
	    cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

		/*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldc, N, C_diff[batch_iter], ldc, work);

	    /*Compute the infinity norm of A */
	    Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, An, test->arrayA[batch_iter], Am, work);

	    /*Compute the one norm of A */
	    ATnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'O', Am, An, test->arrayA[batch_iter], Am, work);

	    /*Compute the infinity norm of B */
	    Bnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Bm, Bn, test->arrayB[batch_iter], Bm, work);

	    /*Compute the one norm of B */
	    BTnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'O', Bm, Bn, test->arrayB[batch_iter], Bm, work);

	    if(test->routine == BBLAS_HER2K)
	    {
		beta_norm = cabs(test->beta_herk[batch_iter]);
	    }else
	    {
		beta_norm = cabs(test->beta[batch_iter]);
	    }

	    result = Rnorm / ((K*cabs(test->alpha[batch_iter])*Anorm*BTnorm +
			       K*cabs(test->alpha[batch_iter])*Bnorm*ATnorm +
			       beta_norm*test->Cinitnorm[batch_iter])*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}

    }else  if( test->batch_opts == BBLAS_FIXED ) //fixed  size
    {
	N = test->N[first_index];
	K = test->K[first_index];
	ldc = test->ldc[first_index];

	if (test->trans[first_index] == BblasNoTrans) {
		Am = N; An = K;
		Bn = K;
	} else {
	    Am = K; An = N;
	    Bn = N;
	}
	Bm = test->ldb[first_index];

	    for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	    {
		/*Compute the error C - C_finial */
		cblas_daxpy (ldc*N, (alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

		/*Compute  the infinity norm associated  with the error */
		Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldc, N, C_diff[batch_iter], ldc, work);

		/*Compute the infinity norm of A */
		Anorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Am, An, test->arrayA[batch_iter], Am, work);

		/*Compute the one norm of A */
		ATnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'O', Am, An, test->arrayA[batch_iter], Am, work);

		/*Compute the infinity norm of B */
		Bnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', Bm, Bn, test->arrayB[batch_iter], Bm, work);

		/*Compute the one norm of B */
		BTnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'O', Bm, Bn, test->arrayB[batch_iter], Bm, work);

		if(test->routine == BBLAS_HER2K)
		{
		    beta_norm = cabs(test->beta_herk[first_index]);
		}else
		{
		    beta_norm = cabs(test->beta[first_index]);
		}

		/*Compute the relative error */
		result = Rnorm / ((K*cabs(test->alpha[first_index])*Anorm*BTnorm +
				   K*cabs(test->alpha[first_index])*Bnorm*ATnorm +
				   beta_norm*test->Cinitnorm[batch_iter])*eps);

		switch(test->target)
		{
		case BBLAS_MKL:
		  test->mkl_error[batch_iter] = result;
		  break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		  test->device_error[batch_iter] = result;
		  break;

		case BBLAS_OTHER:
		  test->other_error[batch_iter] = result;
		  break;
		default:
		  printf("In bblas_dcheck_Cfinal: Fixed, Target no defined\n");
		  exit(EXIT_FAILURE);
		}
	    }
    }else
    {
	bblas_error("bblas_dtesting.c", "wrong batch_opts value");
    }

/*Free allocated memory */
    for(int index =0; index < batch_count; index++)
    {
	free(C_diff[index]);
    }
    free(C_diff);
    free(work);
}

/**
 * bblas_dtrmm_tolerance computes the forward error
 * @f[\|C - C_{\mathrm{seq}}\|_{\infty}/((|\alpha|\|A\|_{\infty}\|B\|_{\infty}N\epsilon).@f]
 *
 **/

void bblas_dtrmm_tolerance(bblas_dtest_t *test, double **B_final)
{
    /*Local variables */
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int Am, An, ldb, N, M;
    int max_work_size = max(test->maxK, test->maxN);
    char u, diag;
    double Anorm, Rnorm, result;
    double eps = LAPACKE_dlamch_work('e');
    double alpha =-1;
    double *work = (double *)malloc(max_work_size*sizeof(double));

    /*Temporary buffer to save (test-arrayB -B_final) */
    double **B_diff;
    B_diff = (double **) malloc(batch_count*sizeof(double *));

    /*Make a copy of test->arrayB in B_diff */
    bblas_dcopy_Binit(test, B_diff);

    /*Variable size */
    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    N = test->N[batch_iter];
	    M = test->M[batch_iter];
	    ldb = test->ldb[batch_iter];

	    Am = test->lda[batch_iter];

	    if(test->side[batch_iter] == BblasLeft )
	    {
		An = M;
	    }else
	    {
		An = N;
	    }

	    if(test->uplo[batch_iter] == BblasLower )
	    {
	       u = 'L';
	    }else
	    {
	       u = 'U';
	    }

	    if(test->diag[batch_iter] == BblasUnit )
	    {
		diag = 'U';
	    }else
	    {
		diag = 'N';
	    }

	    /*Compute the error B - B_finial */
	    cblas_daxpy (ldb*N, (alpha), B_final[batch_iter], 1, B_diff[batch_iter], 1);

		/*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldb, N, B_diff[batch_iter], ldb, work);

	    /*Infinity norm of the triangular matrix A */
	    Anorm = LAPACKE_dlantr_work(LAPACK_COL_MAJOR, 'I', u, diag, Am,
					An, test->arrayA[batch_iter], Am, work);

		result = Rnorm/(N*cabs(test->alpha[batch_iter])*Anorm*test->Binitnorm[batch_iter]*eps);
	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }

	}
    }else  if( test->batch_opts == BBLAS_FIXED )
    {

	N = test->N[first_index];
	M = test->M[first_index];
	ldb = test->ldb[first_index];

	Am = test->lda[first_index];

	if(test->side[first_index] == BblasLeft )
	{
	    An = M;
	}else
	{
	    An = N;
	}

	if(test->uplo[first_index] == BblasLower )
	{
	    u = 'L';
	}else
	{
	    u = 'U';
	}

	if(test->diag[first_index] == BblasUnit )
	{
	    diag = 'U';
	}else
	{
	    diag = 'N';
	}

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    /*Compute the error B - B_finial */
	    cblas_daxpy (ldb*N, (alpha), B_final[batch_iter], 1, B_diff[batch_iter], 1);

		/*Compute  the infinity norm associated  with the error */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldb, N, B_diff[batch_iter], ldb, work);

	    /*Infinity norm of the triangular matrix A */
	    Anorm = LAPACKE_dlantr_work(LAPACK_COL_MAJOR, 'I', u, diag, Am,
					An, test->arrayA[batch_iter], Am, work);

	    result = Rnorm/(N*cabs(test->alpha[first_index])*Anorm*test->Binitnorm[batch_iter]*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}
    }
    else
    {
	bblas_error("bblas_dtesting.c", "wrong batch_opts value");
    }

    /*Free allocated memory */
    for(int index =0; index < batch_count; index++)
    {
	free(B_diff[index]);
    }
    free(B_diff);
    free(work);
}

/**
 * bblas_dtrsm_tolerance computes the backward error
 * @f[\|\alpha B - AX \|_{\infty}/((\|A\|_{\infty}\|X\| + |\alpha|\|B\|_{\infty})N\epsilon).@f]
 *
 **/

void bblas_dtrsm_tolerance(bblas_dtest_t *test, double **B_final)
{
    /*Local variables */
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int Am, An, ldb, N, M;
    int max_work_size = max(test->maxK, test->maxN);
    char u, diag;
    double Anorm, Rnorm, result, Xnorm;
    double eps = LAPACKE_dlamch_work('e');
    double alpha;
    double *work = (double *)malloc(max_work_size*sizeof(double));

    /*Variable to save the residual alphaB -AX */
    double **residual;
    residual = (double **) malloc(batch_count*sizeof(double *));

    /*Make a copy of test->arrayB (X) in residual */
    bblas_dcopy_Binit(test, residual);

    /*Variable size */
    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    N = test->N[batch_iter];
	    M = test->M[batch_iter];
	    ldb = test->ldb[batch_iter];

	    Am = test->lda[batch_iter];

	    if(test->side[batch_iter] == BblasLeft )
	    {
		An = M;
	    }else
	    {
		An = N;
	    }

	    if(test->uplo[batch_iter] == BblasLower )
	    {
	       u = 'L';
	    }else
	    {
	       u = 'U';
	    }

	    if(test->diag[batch_iter] == BblasUnit )
	    {
		diag = 'U';
	    }else
	    {
		diag = 'N';
	    }

	    /*Compute residual <- AX  (dtrmm) */
	    alpha = 1;
	    cblas_dtrmm(BblasColMajor, test->side[batch_iter], test->uplo[batch_iter], test->transA[batch_iter],
			test->diag[batch_iter], M, N, (alpha), test->arrayA[batch_iter],
			test->lda[batch_iter], residual[batch_iter], ldb);

	    /*Compute Binit = alpha Binit */
	    for (int index=0;  index < ldb*N; index++)
	    {
		test->Binit[batch_iter][index] = test->alpha[batch_iter]*test->Binit[batch_iter][index];
	    }

	    /*Compute the  residual <- alpha B - Ax  */
	    alpha= -1;
	    cblas_daxpy (ldb*N, (alpha), test->Binit[batch_iter], 1, residual[batch_iter], 1);
		/*Compute  the infinity norm associated  with the residual */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldb, N, residual[batch_iter], ldb, work);

	    /*Infinity norm of the triangular matrix A */
	    Anorm = LAPACKE_dlantr_work(LAPACK_COL_MAJOR, 'I', u, diag, Am,
					An, test->arrayA[batch_iter], Am, work);

	    /*Compute  the infinity norm associated  with X (B) */
	    Xnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldb, N, test->arrayB[batch_iter], ldb, work);

	    result = Rnorm/(Anorm*Xnorm*N*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }

	}
    }else  if( test->batch_opts == BBLAS_FIXED )
    {

	N = test->N[first_index];
	M = test->M[first_index];
	ldb = test->ldb[first_index];

	Am = test->lda[first_index];

	if(test->side[first_index] == BblasLeft )
	{
	    An = M;
	}else
	{
	    An = N;
	}

	if(test->uplo[first_index] == BblasLower )
	{
	    u = 'L';
	}else
	{
	    u = 'U';
	}

	if(test->diag[first_index] == BblasUnit )
	{
	    diag = 'U';
	}else
	{
	    diag = 'N';
	}

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{

	    /*Compute residual <- AX  (dtrmm) */
	    alpha = 1;
	    cblas_dtrmm(BblasColMajor, test->side[first_index], test->uplo[first_index], test->transA[first_index],
			test->diag[first_index], M, N, (alpha), (const double*) test->arrayA[batch_iter],
			test->lda[first_index], residual[batch_iter], ldb);

	    /*Compute Binit = alpha Binit */
	    for (int index=0;  index < ldb*N; index++)
	    {
		test->Binit[batch_iter][index] = test->alpha[first_index]*test->Binit[batch_iter][index];
	    }

	    /*Compute the  residual <- alpha B - Ax  */
	    alpha = -1;
	    cblas_daxpy (ldb*N, (alpha), test->Binit[batch_iter], 1, residual[batch_iter], 1);

		/*Compute  the infinity norm associated  with the residual */
	    Rnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldb, N, residual[batch_iter], ldb, work);

	    /*Infinity norm of the triangular matrix A */
	    Anorm = LAPACKE_dlantr_work(LAPACK_COL_MAJOR, 'I', u, diag, Am,
					An, test->arrayA[batch_iter], Am, work);

	    /*Compute  the infinity norm associated  with X (B) */
	    Xnorm = LAPACKE_dlange_work(LAPACK_COL_MAJOR, 'I', ldb, N, test->arrayB[batch_iter], ldb, work);

	    result = Rnorm/(Anorm*Xnorm*N*eps);

	    /*Compute the relative error */
	    switch(test->target)
	    {
		case BBLAS_MKL:
		    test->mkl_error[batch_iter] = result;
		    break;

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = result;
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = result;
		    break;
		default:
		    printf("In bblas_dcheck_Cfinal, Variable: Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}
    }
    else
    {
	bblas_error("bblas_dtesting.c", "wrong batch_opts value");
    }

    /*Free allocated memory */
    for(int index =0; index < batch_count; index++)
    {
	free(residual[index]);
    }
    free(residual);
    free(work);
}

/**
 * Calls bblas_dstar2k_tolerance.
 **/
#ifdef COMPLEX
void bblas_dsyr2k_tolerance(bblas_dtest_t *test, double **C_final)
{
    bblas_dstar2k_tolerance(test, C_final);

}
#endif
/**
 * Calls bblas_dstar2k_tolerance.
 **/

void bblas_dsyr2k_tolerance(bblas_dtest_t *test, double **C_final)
{
    bblas_dstar2k_tolerance(test, C_final);
}

/**
 * Calls bblas_dstark_tolerance.
 **/

#ifdef COMPLEX
void bblas_dsyrk_tolerance(bblas_dtest_t *test, double **C_final)
{
    bblas_dstark_tolerance(test, C_final);
}
#endif
/**
 * Calls bblas_dstark_tolerance.
 **/

void bblas_dsyrk_tolerance(bblas_dtest_t *test, double **C_final)
{
    bblas_dstark_tolerance(test, C_final);
}

/**
 * Calls bblas_dstarmm_tolerance.
 **/

#ifdef COMPLEX
void bblas_dsymm_tolerance(bblas_dtest_t *test, double **C_final)
{
    bblas_dstarmm_tolerance(test, C_final);
}
#endif
/**
 * Calls bblas_dstamm_tolerance.
 **/

void bblas_dsymm_tolerance(bblas_dtest_t *test, double **C_final)
{
    bblas_dstarmm_tolerance(test, C_final);
}


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
#include "parmt_mtsearch.h"
#ifdef PARMT_USE_INTEL
#include <mkl_cblas.h>
#else
#include <cblas.h>
#endif
#include "iscl/array/array.h"
#include "iscl/memory/memory.h"
#include "iscl/signal/convolve.h"

int parmt_computeStackedCrossCorrelation_MPI(
    const MPI_Comm comm,
    const int npgrns,
    const double *__restrict__ Gxx,
    const double *__restrict__ Gyy,
    const double *__restrict__ Gzz,
    const double *__restrict__ Gxy,
    const double *__restrict__ Gxz,
    const double *__restrict__ Gyz,
    const int ldm, const int nmt, const double *__restrict__ mt,
    const int npts, const double *__restrict__ data,
    const int lxc, double *__restrict__ xc)
{
    double *xcwork;
    int ierr, lx;
    ierr = 0;
    lx = npts + npgrns - 1;
    xcwork = memory_calloc64f(lxc);
    ierr = parmt_computeStackedCrossCorrelation(npgrns,
                                                Gxx, Gyy, Gzz,
                                                Gxy, Gxz, Gyz,
                                                ldm, nmt, mt,
                                                npts, data,
                                                lx, xcwork);
    MPI_Allreduce(xcwork, xc, lxc, MPI_DOUBLE, MPI_SUM, comm);
    memory_free64f(&xcwork);
    return ierr;
}
//============================================================================//
/*!
 * @brief This is an alignment tool intended to optimize the lag by computing
 *
 *        \f[
 *           x_c = \frac{1}{n_{mt}} \sum_{i=1}^{n_{mt}} \textbf{u}_i \textbf{d}
 *        \f]
 *
 *        where the \f$ \star \f$ implies a normalized cross-correlation.
 *
 * @param[in] npgrns    number of points in Green's functions
 * @param[in] Gxx       Green's functions scaled by \f$ m_{xx} \f$ [npgrns]
 * @param[in] Gyy       Green's functions scaled by \f$ m_{yy} \f$ [npgrns]
 * @param[in] Gzz       Green's functions scaled by \f$ m_{zz} \f$ [npgrns]
 * @param[in] Gxy       Green's functions scaled by \f$ m_{xy} \f$ [npgrns]
 * @param[in] Gxz       Green's functions scaled by \f$ m_{xz} \f$ [npgrns]
 * @param[in] Gyz       Green's functions scaled by \f$ m_{yz} \f$ [npgrns]
 * @param[in] ldm       leading dimension of moment tensors (>= 6)
 * @param[in] nmt       number of moment tensors
 * @param[in] mt        moment tensors.  the i'th moment tensor begins at
 *                      i*ldm and the moment tensor terms are packed
 *                      \f$ \{ m_{xx}, m_{yy}, m_{zz},
 *                             m_{xy}, m_{xz}, m_{yz} \f$.
 * @param[in] npts      number of points in signal
 * @param[in] data      Observed data.  This is an array of dimension [npts].
 * @param[in] lxc       max size of xc (should be >= npts + npgrns - 1)
 *
 * @param[out] xc       stack of the observed and synthetic cross-correlations
 *                      [lxc].  the C indexed lag is computed:
 *                        npts - 1 + argmax(xc)
 *                      where the argmax takes values [0,lx-1]
 *
 * @result 0 indicates success
 *
 * @author Ben Baker
 *
 * @copyright ISTI distributed under Apache 2
 *
 */
int parmt_computeStackedCrossCorrelation(
    const int npgrns,
    const double *__restrict__ Gxx,
    const double *__restrict__ Gyy,
    const double *__restrict__ Gzz,
    const double *__restrict__ Gxy,
    const double *__restrict__ Gxz,
    const double *__restrict__ Gyz,
    const int ldm, const int nmt, const double *__restrict__ mt,
    const int npts, const double *__restrict__ data,
    const int lxc, double *__restrict__ xc)
{
    const char *fcnm = "parmt_computedStackedCrossCorrelation\0";
    double *Gmat, *Gxc, *xcorr, *xcw;
    double m6[8] __attribute__((aligned(64)));
    double dscal __attribute__((aligned(64))) = 0.0;
    double gscal __attribute__((aligned(64))) = 0.0;
    double xscal __attribute__((aligned(64))) = 0.0;
    double est __attribute__((aligned(64))) = 0.0;
    int i, ierr, j, lc;
    lc = npts + npgrns - 1;
    if (npts < 1 || npgrns < 1 ||
        Gxx == NULL || Gyy == NULL || Gzz == NULL ||
        Gxy == NULL || Gxz == NULL || Gyz == NULL ||
        data == NULL)
    {
        if (npts < 1){fprintf(stderr, "%s: no data points\n", __func__);}
        if (npgrns < 1){fprintf(stderr, "%s: no grns fns points\n", __func__);}
        if (data == NULL){fprintf(stderr, "%s: data is NULL\n", __func__);}
        if (Gxx == NULL){fprintf(stderr, "%s: Gxx is NULL\n", __func__);}
        if (Gyy == NULL){fprintf(stderr, "%s: Gyy is NULL\n", __func__);}
        if (Gzz == NULL){fprintf(stderr, "%s: Gzz is NULL\n", __func__);}
        if (Gxy == NULL){fprintf(stderr, "%s: Gxy is NULL\n", __func__);}
        if (Gxz == NULL){fprintf(stderr, "%s: Gxz is NULL\n", __func__);}
        if (Gyz == NULL){fprintf(stderr, "%s: Gyz is NULL\n", __func__);}
        return -1;
    }
    if (lc > lxc)
    {
        fprintf(stderr, "%s: Error xc is too small\n", __func__);
        return -1;
    }
    if (ldm < 6 || nmt < 1 || mt == NULL)
    {
        if (ldm < 6){fprintf(stderr, "%s: ldm must be >= 6\n", __func__);}
        if (nmt < 1){fprintf(stderr, "%s: no moment tensors\n", __func__);}
        if (mt == NULL){fprintf(stderr, "%s: mt is NULL\n", __func__);}
        return -1;
    }
    // Set space and correlate data with columns of G
    xcorr = memory_calloc64f(lc);
    Gmat = memory_calloc64f(8*npgrns);
    Gxc = memory_calloc64f(8*lc);
    dscal = cblas_dnrm2(npts, data, 1);
    dscal = dscal*dscal;
#ifdef __INTEL_COMPILER
__assume_aligned(Gxc, 64);
__assume_aligned(Gmat, 64);
__assume_aligned(xcorr, 64);
__assume_aligned(xc, 64);
#endif
    for (i=0; i<6; i++)
    {
        if (i == 0)
        {
            cblas_dcopy(npgrns, Gxx, 1, &Gmat[i], 8);
            ierr = signal_convolve_correlate64f_work(npts, data,
                                                     npgrns,
                                                     Gxx,
                                                     CONVCOR_FULL,
                                                     lc, xcorr);
        }
        else if (i == 1)
        {
            cblas_dcopy(npgrns, Gyy, 1, &Gmat[i], 8);
            ierr = signal_convolve_correlate64f_work(npts, data,
                                                     npgrns,
                                                     Gyy,
                                                     CONVCOR_FULL,
                                                     lc, xcorr);
        }
        else if (i == 2)
        {
            cblas_dcopy(npgrns, Gzz, 1, &Gmat[i], 8);
            ierr = signal_convolve_correlate64f_work(npts, data,
                                                     npgrns,
                                                     Gzz,
                                                     CONVCOR_FULL,
                                                     lc, xcorr);
        }
        else if (i == 3)
        {
            cblas_dcopy(npgrns, Gxy, 1, &Gmat[i], 8);
            ierr = signal_convolve_correlate64f_work(npts, data,
                                                     npgrns,
                                                     Gxy,
                                                     CONVCOR_FULL,
                                                     lc, xcorr);
        }
        else if (i == 4)
        {
            cblas_dcopy(npgrns, Gxz, 1, &Gmat[i], 8);
            ierr = signal_convolve_correlate64f_work(npts, data,
                                                     npgrns,
                                                     Gxz,
                                                     CONVCOR_FULL,
                                                     lc, xcorr);
        }
        else if (i == 5)
        {
            cblas_dcopy(npgrns, Gyz, 1, &Gmat[i], 8);
            ierr = signal_convolve_correlate64f_work(npts, data,
                                                     npgrns,
                                                     Gyz,
                                                     CONVCOR_FULL,
                                                     lc, xcorr);
        }
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error correlating Greens fn: %d\n",
                    __func__, i+1);
            return -1;
        }
        cblas_dcopy(lc, xcorr, 1, &Gxc[i], 8);
    }
    array_zeros64f_work(lxc, xc);
    // Compute \f$ x_c = \sum_{i=1}^{n_{mt}} G \textbf{m} \star \textbf{d} \f$
#pragma omp parallel shared(Gxc, Gmat, mt, lc, xc) \
        firstprivate (dscal, xcorr) \
        private (est, gscal, xscal, i, j, m6, xcw) default(none)
{
    xcw = memory_calloc64f(lxc);
#ifdef __INTEL_COMILER
__assume_aligned(xcw, 64);
#endif
    #pragma omp for
    for (i=0; i<nmt; i++)
    {
        m6[0] = mt[ldm*i  ];
        m6[1] = mt[ldm*i+1];
        m6[2] = mt[ldm*i+2];
        m6[3] = mt[ldm*i+3];
        m6[4] = mt[ldm*i+4];
        m6[5] = mt[ldm*i+5];
        // compute the energy in the synthetic
        gscal = 0.0;
        for (j=0; j<npgrns; j++)
        {
            est = Gmat[8*j+0]*m6[0] + Gmat[8*j+1]*m6[1] + Gmat[8*j+2]*m6[2]
                + Gmat[8*j+3]*m6[3] + Gmat[8*j+4]*m6[4] + Gmat[8*j+5]*m6[5];
            gscal = gscal + est*est;
        }
        xscal = 1.0/sqrt(dscal*gscal);
        // compute the normalized cross-correlation
        for (j=0; j<lc; j++)
        {
            est = Gxc[8*j+0]*m6[0] + Gxc[8*j+1]*m6[1] + Gxc[8*j+2]*m6[2]
                + Gxc[8*j+3]*m6[3] + Gxc[8*j+4]*m6[4] + Gxc[8*j+5]*m6[5];
            xcw[j] = xcw[j] + xscal*est;
        }
        //cblas_dgemv(CblasRowMajor, CblasNoTrans,
        //            lc, 6, 1.0, G, 8, &mt[8*i], 1, 1.0, xcw, 1);
    }
    #pragma omp critical
    #pragma omp simd
    for (i=0; i<lxc; i++)
    {
        xc[i] = xc[i] + xcw[i];
    }
    memory_free64f(&xcw);
}
    // normalize by the number of moment tensors
    cblas_dscal(lc, 1.0/(double) nmt, xc, 1);
    // free memory
    memory_free64f(&Gxc);
    memory_free64f(&Gmat);
    memory_free64f(&xcorr);
    return 0;
}


/*
/
/ adkGSL.c
/
/ homebrewed addons to gsl */

#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_sort_vector.h>
#include <gsl/gsl_cblas.h>
#include <gsl/gsl_statistics.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include "../extern/include/f2c.h"
#include "../extern/include/blaswrap.h"
#include "../extern/include/clapack.h"
//#include <vecLib/cblas.h>
//#include <accelerate/accelerate.h>
#include "adkGSL.h"

#define SUM_LOG_THRESHOLD  -10

//gsl_matrix_covariance-- computes sample covariance matrix and stores it in matrix cov
void gsl_matrix_covariance(gsl_matrix *data, gsl_matrix *cov){
	gsl_vector_view a, b;
	size_t i, j;
	double v;
	for (i = 0; i < data->size2; i++) {
 		for (j = 0; j < data->size2; j++) {
 			a = gsl_matrix_column (data, i);
 			b = gsl_matrix_column (data, j);
 			v = gsl_stats_covariance (a.vector.data, a.vector.stride,
 				b.vector.data, b.vector.stride, a.vector.size);
 			gsl_matrix_set (cov, i, j, v);
		}
	}
}

/*gsl_vector_includes() - returns 1 or 0 depending on result */
int gsl_vector_includes(gsl_vector *aVec, double aValue){
  int i;

  for(i = 0; i < aVec->size; i++){
    if (gsl_vector_get(aVec, i) == aValue){
      return(1);
    }
  }
  return(0);
}

double gsl_vector_sum(gsl_vector *aVec, int aVecSize){
	double sum;
	int i;

	sum = 0;
	for (i = 0; i < aVecSize; i++){
		sum += gsl_vector_get(aVec, i);
		}
	return sum;
}

double gsl_vector_dot_product(gsl_vector *vec1, gsl_vector *vec2){
  int i;
  double dot = 0.0;

  assert(vec1->size == vec2->size);
  for(i = 0; i < vec1->size; i++){
    dot += gsl_vector_get(vec1, i) *  gsl_vector_get(vec2, i);
  }
  return(dot);
}

void gsl_vector_outer_product(gsl_vector *vec1, gsl_vector *vec2, gsl_matrix *result){
	int i,j;

	assert(vec1->size == result->size1 || vec2->size == result->size2);
	for(i = 0; i < vec1->size; i++){
		for(j = 0; j < vec2->size; j++){
			gsl_matrix_set(result,i,j,gsl_vector_get(vec1,i) * gsl_vector_get(vec2,j));
		}
	}
}

double gsl_matrix_row_sum(gsl_matrix *mat, int iRow, int rowSize){
	gsl_vector_view aRow;
	double sum = 0;
	int i;

	aRow = gsl_matrix_row(mat, iRow);
	for (i = 0; i< rowSize; i++){
		sum += gsl_vector_get(&aRow.vector, i);
		}
	return sum;
}

/* efficiently compute log of sum of values, which themselves are
   stored as logs: that is, return log(sum_i exp(l_i)).  The largest
   of the elements of l (call it maxval) is factored out, so that
   log(sum_i(exp(l_i))) = maxval + log(1 + sum_i(exp(l_i-maxval))),
   where the new sum is taken over 2 <= i < n.  All of the quantities
   in the exp must be negative, and those smaller than some reasonable
   threshold can be ignored. [Thanks to David Haussler for showing me
   this trick].  This code was adapted from code kindly given to me by Adam Siepel */

double log_sum(gsl_vector *vec) {
  double maxval, expsum;
  int k;

  if (vec->size > 1){
  	gsl_sort_vector(vec);
	gsl_vector_reverse(vec);
	}

  maxval = gsl_vector_get(vec, 0);
  expsum = 1;
  k = 1;

  while (k < vec->size && gsl_vector_get(vec, k) - maxval > SUM_LOG_THRESHOLD){
    expsum += exp(gsl_vector_get(vec, k++) - maxval);
	}
  return maxval + log(expsum);
}

/*the following methods are wrappers for use with LAPACK */

void set_lapack_entry(double *A, int i, int j, int nrows, double val){
  A[j*nrows+i] = val;
}

double get_lapack_entry(double *a, int i, int j,int nrows){
  printf("ha");
  return a[j*nrows+i];
}

/*this is meant to be a conversion utility */
double *gsl_matrix_2_lapack(gsl_matrix *m){
  double *lapack_mat;
  int i, j;

  lapack_mat = malloc(m->size1 * m->size2 * sizeof(double));
  for(i = 0; i < m->size1; i++){
    for(j = 0; j < m->size2; j++){
      set_lapack_entry(lapack_mat, i, j,m->size1, gsl_matrix_get(m, i, j));
    }
  }
  return lapack_mat;
}

gsl_matrix *lapack_2_gsl_matrix(double *A, int nrows, int ncols){
  int i, j;
  gsl_matrix *new;

  new = gsl_matrix_alloc(nrows,ncols);
  for(i=0;i<nrows;i++){
    for(j=0;j<ncols;j++){
      gsl_matrix_set(new,i,j,get_lapack_entry(A,i,j,nrows));
    }
  }
  return new;
}

gsl_matrix *gsl_matrix_power_logs(gsl_matrix *aMatrix, int power){
  gsl_matrix *powerMat, *logMat;
  int i, j;
  double x;

  powerMat = gsl_matrix_power(aMatrix, power);
  logMat = gsl_matrix_alloc(aMatrix->size1, aMatrix->size2);
  for(i = 0; i < aMatrix->size1; i++){
    for(j = 0; j < aMatrix->size2; j++){
      x = gsl_matrix_get(powerMat, i, j);
      gsl_matrix_set(logMat, i, j, log(x));
    }
  }
  gsl_matrix_free(powerMat);
  return(logMat);
}

gsl_matrix *gsl_matrix_power(gsl_matrix *aMatrix, int power){
  gsl_matrix *revect, *levect;
  double *dataMat, *wr, *wi, *vl, *vr, *aMat, *bMat, *cMat, *dMat, *work;
  int lwork, i, j, n ,info;
  assert(aMatrix->size1 == aMatrix->size2);
  n = aMatrix->size1;

  if (power == 1){
    gsl_matrix * solMat = gsl_matrix_alloc(n, n);
    for(i=0; i < n; i++){
      for(j = 0; j< n; j++){
	gsl_matrix_set(solMat, i, j, gsl_matrix_get(aMatrix,i,j));
      }
    }
    return(solMat);
  }

  //allocate space for eigenvector matrices, and other result holders
  levect = gsl_matrix_alloc(n, n);
  revect = gsl_matrix_alloc(n, n);
  wr = malloc(n * sizeof(double));
  wi =  malloc(n * sizeof(double));
  vl = malloc(n * n * sizeof(double));
  vr = malloc(n * n * sizeof(double));
  lwork = 20 * n;
  work = malloc(lwork * sizeof(double));

  //move data to LAPACK (col major) format
  dataMat =  gsl_matrix_2_lapack(aMatrix);

  //run LAPACK routine dgeev
  #ifdef OSX
  dgeev('V','V',n, dataMat, n, wr, wi, vl, n, vr, n, work, lwork);
  #else
  dgeev_('V','V',n, dataMat, n, wr, wi, vl, n, vr, n, work, lwork, info);
  #endif
  //collect eigenvalues into matrices
  for(i=0; i < n; i++){
    for(j = 0; j< n; j++){
      gsl_matrix_set(revect, i, j, vr[j*2+i]);
      gsl_matrix_set(levect, j, i, vl[j*2+i]);
    }
  }

  /* rescale such that revect and levect are inverses */
  /* see Press et al. (Numerical Recipes) for a very clear explanation
     of the relationship between the left and right eigenvectors  */
   for(i = 0; i < n; i++){
    double dotProd = 0.0;
    /* compute dot product of row i in levect and col i in revect */
    for(j = 0; j< n; j++){
      dotProd += gsl_matrix_get(levect, i, j) * gsl_matrix_get(revect,j,i);
      }

      /* now rescale levect */
    for(j = 0; j< n; j++){
      double old = gsl_matrix_get(levect, i, j);
      double scaled = old / dotProd;
      gsl_matrix_set(levect, i, j, scaled);
      }
  }

   //prepare for matrix multiplication
   gsl_matrix * solMat = gsl_matrix_alloc(n, n);
   gsl_matrix * diag = gsl_matrix_alloc(n, n);
   gsl_matrix_set_all(diag, 0);
   for(j = 0; j< n; j++){
     gsl_matrix_set(diag, j,j, pow(wr[j],power));
   }

   aMat = gsl_matrix_2_lapack(diag);
   bMat = gsl_matrix_2_lapack(revect);
   cMat = malloc(n * n * sizeof(double));

   //compute a^t = s^-1 * diag^t * s
   cblas_dgemm(CblasColMajor, CblasNoTrans, CblasTrans, n, n, n, 1.0, aMat, n, bMat, n, 0.0, cMat, n);
   free(bMat);

   bMat = gsl_matrix_2_lapack(levect);
   dMat =  malloc(n * n * sizeof(double));
   cblas_dgemm(CblasColMajor, CblasTrans, CblasNoTrans, n, n, n, 1, cMat, n, bMat, n, 0.0, dMat, n);

   //put results into gsl_matrix
   for(i = 0; i < n; i++){
     for(j = 0; j < n; j++){
       gsl_matrix_set(solMat, i, j, dMat[j*n+i]);
     }
   }

   //cleanup
   gsl_matrix_free(levect);
   gsl_matrix_free(revect);
   gsl_matrix_free(diag);
   free(work);
   free(vl);
   free(vr);
   free(wi);
   free(wr);
   free(aMat);
   free(bMat);
   free(cMat);
   free(dMat);
   free(dataMat);
   return(solMat);
}

//returns the trace of a matrix-- sum of diag
double gsl_matrix_trace(gsl_matrix *x){
	int i;
	double sum = 0.0;
	for(i=0;i<x->size1;i++){
		sum += gsl_matrix_get(x,i,i);
	}
	return(sum);
}

//copies the lower triangle of src in to dest, sets others to zero
void gsl_matrix_lower_tri_copy(gsl_matrix *src, gsl_matrix *dest){
	int i, j;
	assert(src->size1 == dest->size1);
	assert(src->size2 == dest->size2);
	for(i = 0; i < src->size1; i++){
		for(j = 0; j < src->size2; j++){
			if(i<=j){
				gsl_matrix_set(dest,i,j,gsl_matrix_get(src,i,j));
			}
			else{
				gsl_matrix_set(dest,i,j,0.0);
			}
		}
	}
}

//copies the upper triangle of src in to dest, sets others to zero
void gsl_matrix_upper_tri_copy(gsl_matrix *src, gsl_matrix *dest){
	int i, j;
	assert(src->size1 == dest->size1);
	assert(src->size2 == dest->size2);
	for(i = 0; i < src->size1 ;i++){
		for(j = 0; j < src->size2; j++){
			if(i>=j){
				gsl_matrix_set(dest,i,j,gsl_matrix_get(src,i,j));
			}
			else{
				gsl_matrix_set(dest,i,j,0.0);
			}
		}
	}
}

//zeros out everything but lower tri-- in place
void gsl_matrix_lower_tri(gsl_matrix *x){
	int i, j;
	for(i = 0; i < x->size1; i++){
		for(j = 0; j < x->size2; j++){
			if(i>j)	gsl_matrix_set(x,i,j,0.0);
		}
	}
}

//zeros out everything but upper tri-- in place
void gsl_matrix_upper_tri(gsl_matrix *x){
		int i, j;
		for(i = 0; i < x->size1; i++){
			for(j = 0; j < x->size2; j++){
				if(i<j)	gsl_matrix_set(x,i,j,0.0);
			}
		}
}

//fillMatrixFromArray-- fills up a matrix based on nrow and ncol given *double
void fillMatrixFromArray(double *numbers, gsl_matrix *dest, int nrow, int ncol){
	int i, j, count = 0;
	assert(nrow == dest->size1);
	for(i = 0; i < nrow; i++){
		for(j=0;j<ncol;j++){
			gsl_matrix_set(dest,i,j,numbers[count++]);
		}
	}
}

//fillMatrixFromArray-- fills up a matrix based on nrow and ncol given gsl_vector
void fillMatrixFromVector(gsl_vector *numbers, gsl_matrix *dest, int nrow, int ncol){
	int i, j, count = 0;
	assert(nrow == dest->size1);
	for(i = 0; i < nrow; i++){
		for(j=0;j<ncol;j++){
			gsl_matrix_set(dest,i,j,gsl_vector_get(numbers,count++));
		}
	}
}

//fillArrayFromMatrix-- fills up a vector  based gsl_matrix
void fillArrayFromMatrix(gsl_matrix *src, gsl_vector *dest){
	int i, j, count = 0;

	for(i = 0; i < src->size1; i++){
		for(j=0;j<src->size2;j++){
			gsl_vector_set(dest,count++, gsl_matrix_get(src,i,j));
		}
	}
}

//fillMatrixFromCholArray-- fills up a matrix based on nrow and ncol given arraay
// representing the Cholesky decomp
void fillMatrixFromCholArray(double *numbers, gsl_matrix *dest, int nrow, int ncol){
	int i, j, count = 0;

	for(i = 0; i < nrow; i++){
		for(j=0;j<ncol;j++){
			if(i<=j)gsl_matrix_set(dest,i,j,numbers[count++]);
		}
	}
}

void fillCholArrayFromMatrix(gsl_matrix *src, gsl_vector *dest){
	int i, j, count = 0;

	for(i = 0; i < src->size1; i++){
		for(j=0;j<src->size2;j++){
			if(i<=j){
				 gsl_vector_set(dest,count++, gsl_matrix_get(src,i,j));
				//printf("i:%d j: %d val: %f\n",i,j,gsl_matrix_get(src,i,j) );
			}

		}
	}

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef OSX

/*some wrappers for use with the OS X vecLib LAPACK implementation */
/*wrapper for the lapack eigensystem routine dgeev_*/

int dgeev(char jobvl, char jobvr, int n,  double *a, int lda, double *wr, double *wi, double *vl,
	  int ldvl,double *vr, int ldvr, double *work, int lwork){

  __CLPK_integer _n, _lda, _ldvl, _ldvr, _lwork, info;

  _n = (__CLPK_integer) n;
  _lda =  (__CLPK_integer) lda;
  _ldvl =(__CLPK_integer) ldvl;
  _ldvr =(__CLPK_integer) ldvr;
  _lwork =(__CLPK_integer) lwork;

  dgeev_(&jobvl, &jobvr, &_n, a, &_lda, wr, wi, vl, &_ldvl, vr, &_ldvr, work, &_lwork, &info);
  return info;
}

/*wrapper for the lapack eigensystem routine dsyev_
int dsyev(char jobvl, char uplo, int n,  double *a, int lda, double *w, double *work, int lwork){

  __CLPK_integer _n, _lda, _lwork, info;

  _n = (__CLPK_integer) n;
  _lda =  (__CLPK_integer) lda;
  _lwork =(__CLPK_integer) lwork;

  dsyev_(&jobvl, &uplo, &_n, a, &_lda, w, work, &_lwork, &info);
  return info;
}

int dgehrd(int n, int ilo, int ihi, double *a, int lda, double *tau, double *work, int lwork){
  __CLPK_integer _n, _ilo, _ihi, _lda,  _lwork, info;

   _n = (__CLPK_integer) n;
  _lda =  (__CLPK_integer) lda;
  _lwork =(__CLPK_integer) lwork;
  _ilo =(__CLPK_integer) ilo;
  _ihi =(__CLPK_integer) ihi;

  dgehrd_(&_n, &_ilo, &_ihi, a, &_lda, tau, work, &_lwork, &info);
  return info;
}

int dgebal(char job, int n,  double *a, int lda, int ilo, int ihi, double *scale){
  __CLPK_integer _n, _ilo, _ihi, _lda, info;

   _n = (__CLPK_integer) n;
  _lda =  (__CLPK_integer) lda;
  _ilo =(__CLPK_integer) ilo;
  _ihi =(__CLPK_integer) ihi;

  dgebal_(&job, &_n, a, &_lda, &_ilo, &_ihi, scale, &info);
  return info;
}

int dhseqr(char job, char compz, int n, int ilo, int ihi,double *h, int ldh, double *wr, double *wi, double *z, int ldz, double *work, int lwork){
  __CLPK_integer _n, _ilo, _ihi, _ldh, _ldz, _lwork, info;

   _n = (__CLPK_integer) n;
  _ldh =  (__CLPK_integer) ldh;
  _ilo =(__CLPK_integer) ilo;
  _ihi =(__CLPK_integer) ihi;
  _ldz =  (__CLPK_integer) ldz;
  _lwork = (__CLPK_integer) lwork;
  dhseqr_(&job, &compz, &_n, &_ilo, &_ihi, h, &_ldh, wr, wi, z,&_ldz,  work, &_lwork, &info);
  return info;
}
#endif

int dgeev(char jobvl, char jobvr, int n,  double *a, int lda, double *wr, double *wi, double *vl,
          int ldvl,double *vr, int ldvr, double *work, int lwork){

  long int _n, _lda, _ldvl, _ldvr, _lwork, info;

  _n =  n;
  _lda =   lda;
  _ldvl = ldvl;
  _ldvr = ldvr;
  _lwork = lwork;

  dgeev_(&jobvl, &jobvr, &_n, a, &_lda, wr, wi, vl, &_ldvl, vr, &_ldvr, work, &_lwork, &info);
  return info;
}

int dsyev(char jobvl, char uplo, int n,  double *a, int lda, double *w, double *work, int lwork){

  long int _n, _lda, _lwork, info;

  _n =  n;
  _lda =   lda;
  _lwork = lwork;

  dsyev_(&jobvl, &uplo, &_n, a, &_lda, w, work, &_lwork, &info);
  return info;
}

int dgehrd(int n, int ilo, int ihi, double *a, int lda, double *tau, double *work, int lwork){
  long int _n, _ilo, _ihi, _lda,  _lwork, info;

   _n =  n;
  _lda =   lda;
  _lwork = lwork;
  _ilo = ilo;
  _ihi = ihi;

  dgehrd_(&_n, &_ilo, &_ihi, a, &_lda, tau, work, &_lwork, &info);
  return info;
}

int dgebal(char job, int n,  double *a, int lda, int ilo, int ihi, double *scale){
  long int _n, _ilo, _ihi, _lda, info;

   _n =  n;
  _lda =   lda;
  _ilo = ilo;
  _ihi = ihi;

  dgebal_(&job, &_n, a, &_lda, &_ilo, &_ihi, scale, &info);
  return info;
}

int dhseqr(char job, char compz, int n, int ilo, int ihi,double *h, int ldh, double *wr, double *wi, double *z, int ldz, double *work, int lwork){
  long int _n, _ilo, _ihi, _ldh, _ldz, _lwork, info;

   _n =  n;
  _ldh =   ldh;
  _ilo = ilo;
  _ihi = ihi;
  _ldz =   ldz;
  _lwork =  lwork;
  dhseqr_(&job, &compz, &_n, &_ilo, &_ihi, h, &_ldh, wr, wi, z,&_ldz,  work, &_lwork, &info);
  return info;
}
*/
#endif


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_blas.h>

#define m(i,j) m[i*l+j]

double* calcMys(char m[],int n,int l)
{
  int i,j,k,k1,k2,k3,k4;
  char reslist[]={ 'A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K',
      'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'Y'};
  int resnum=sizeof(reslist)/sizeof(char);
  double *simi;
  simi=malloc(n*n*sizeof(double));
  for (i=0;i<n;i++)
  {
    simi[i*n+i]=0.0;
    for (j=i+1;j<n;j++)
    {
      double allcount=0,same=0;
      for (k=0;k<l;k++)
      {
        if (m(i,k)!='-' || m(j,k)!='-')
        {
          allcount++;
          if (m(i,k)==m(j,k))
            same++;
        }
      }
      simi[i*n+j]=same/allcount;
      simi[j*n+i]=simi[i*n+j];
    }
  }
  int *edge,edgenow=0;
  edge=malloc(5*n*2*sizeof(int));
  for (i=0;i<5*n*2;i++)
  {
    edge[i]=-1;
  }
  for (i=0;i<n;i++)
  {
    int has=0,now=0,maxn[5],max5=-1;
    while (has<5)
    {
      if (now!=i)
      {
        maxn[has]=now;
        has++;
      }
      now++;
    }
    for(j=0;j<5;j++)
      if (max5==-1||simi[i*n+maxn[max5]]>simi[i*n+maxn[j]])
        max5=j;
    for (j=now;j<n;j++)
    {
      if (simi[i*n+j]>simi[i*n+maxn[max5]])
      {
        maxn[max5]=j;
        max5=-1;
        for(k=0;k<5;k++)
          if (max5==-1||simi[i*n+maxn[max5]]>simi[i*n+maxn[k]])
            max5=k;
      }
    }
    for (j=0;j<5;j++)
    {
      edge[edgenow*2+0]=i;
      edge[edgenow*2+1]=maxn[j];
      edgenow++;
    }
  }
  free(simi);
  double *mys;
  int n1=0,n2=0;
  mys=malloc(l*l*sizeof(double));
  for (i=0;i<l*l;i++)
    mys[i]=0.0;
  for (i=0;i<l;i++)
  {
    for (j=i+1;j<l;j++)
    {
      double *prop,*pro1,*pro2,allnumber=0,add=0;
      prop=malloc(20*20*20*20*sizeof(double));
      pro1=malloc(20*20*sizeof(double));
      pro2=malloc(20*20*sizeof(double));
      int count1=0,count2=0,count3=0,count4=0;
      for (k=0;k<160000;k++)
        prop[k]=0.0;
      for (k=0;k<400;k++)
      {
        pro1[k]=0;
        pro2[k]=0;
      }

      for (k=0;k<5*n;k++)
      {
        n1=edge[k*2];
        n2=edge[k*2+1];
        if (m(n1,i)!='-' && m(n1,j)!='-' && m(n2,i)!='-' && m(n2,j)!='-')
        {
          count1=0;
          count2=0;
          count3=0;
          count4=0;
          for (k1 = 0; k1 < 20; k1++)
          {
            if (reslist[k1]==m(n1,i))
            {
              count1=k1;
              break;
            }
          }
          for (k1 = 0; k1 < 20; k1++)
          {
            if (reslist[k1]==m(n2,i))
            {
              count2=k1;
              break;
            }
          }
          for (k1 = 0; k1 < 20; k1++)
          {
            if (reslist[k1]==m(n1,j))
            {
              count3=k1;
              break;
            }
          }
          for (k1 = 0; k1 < 20; k1++)
          {
            if (reslist[k1]==m(n2,j))
            {
              count4=k1;
              break;
            }
          }
          prop[(count1*20+count3)*400+(count2*20+count4)]+=1;
          prop[(count2*20+count4)*400+(count1*20+count3)]+=1;
          allnumber+=2;
        }
      }

      gsl_matrix_view m = gsl_matrix_view_array (prop, 400, 400);
      gsl_vector *eval = gsl_vector_alloc (400);
      gsl_matrix *evec = gsl_matrix_alloc (400, 400);
      gsl_eigen_symmv_workspace * w = gsl_eigen_symmv_alloc (400);
      gsl_eigen_symmv (&m.matrix, eval, evec, w);
      gsl_eigen_symmv_free (w);
      gsl_eigen_symmv_sort (eval, evec, GSL_EIGEN_SORT_ABS_ASC);
      gsl_matrix *mid = gsl_matrix_alloc (400, 400);
      gsl_matrix *right = gsl_matrix_alloc (400, 400);
      for (k1 = 0; k1 < 400; k1++)
      {
        double ser=gsl_vector_get(eval,k1);
        for (k2=0;k2<400;k2++)
        {
          gsl_matrix_set(mid,k1,k2,0);
          gsl_matrix_set(right,k1,k2,gsl_matrix_get(evec,k2,k1));
        }
        gsl_matrix_set(mid,k1,k1,ser/(ser+1));
      }
      gsl_matrix *temp= gsl_matrix_alloc (400, 400);
      gsl_blas_dgemm (CblasNoTrans, CblasNoTrans,1.0, evec, mid,
                  0.0, temp);
      gsl_blas_dgemm (CblasNoTrans, CblasNoTrans,1.0, temp, right,
                  0.0, evec);
      gsl_matrix_free(mid);
      gsl_matrix_free(right);
      gsl_vector_free (eval);
      for (k1=0;k1<400;k1++)
        for(k2=0;k2<400;k2++)
        {
          prop[k1*400+k2]=gsl_matrix_get(evec,k1,k2);
          if (prop[k1*400+k2]<0)
            prop[k1*400+k2]=-prop[k1*400+k2];
        }
      gsl_matrix_free (evec);
      gsl_matrix_free (temp);
      for (k1=0;k1<20;k1++)
        for(k2=0;k2<20;k2++)
          for(k3=0;k3<20;k3++)
            for(k4=0;k4<20;k4++)
            {
              pro1[k1*20+k2]+=prop[(k1*20+k3)*400+(k2*20+k4)];
              pro2[k3*20+k4]+=prop[(k1*20+k3)*400+(k2*20+k4)];
            }
      if (allnumber!=0)
      {
        for (k=0;k<160000;k++)
          prop[k]=prop[k]/allnumber;
        for (k=0;k<400;k++)
        {
          pro1[k]=pro1[k]/allnumber;
          pro2[k]=pro2[k]/allnumber;
        }
      }
      allnumber=0.0;
      for (k1=0;k1<20;k1++)
        for (k2=0;k2<20;k2++)
        {
          if (pro1[k1*20+k2]==0)
          {
            continue;
          }
          else
          {
            for (k3=0;k3<20;k3++)
              for (k4=0;k4<20;k4++)
              {
                if (pro2[k3*20+k4]==0||prop[(k1*20+k3)*400+(k2*20+k4)]==0)
                {
                  continue;
                }
                else
                {
                  allnumber+=prop[(k1*20+k3)*400+(k2*20+k4)]*log(prop[(k1*20+k3)*400+(k2*20+k4)]/pro1[k1*20+k2]/pro2[k3*20+k4]);
                }
              }
          }
        }
      mys[i*l+j]=allnumber;
      mys[j*l+i]=allnumber;
      free(prop);
      free(pro1);
      free(pro2);
    }
  }
  free(edge);
  return mys;
}