#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <gsl/gsl_statistics_double.h>
#include "tz_error.h"
#include "tz_constant.h"
#include "tz_iarray.h"
#include "tz_image_lib.h"
#include "tz_objdetect.h"
#include "tz_imatrix.h"
#include "tz_stack_math.h"
#include "tz_stack_bwdist.h"
#include "tz_pixel_array.h"
#include "tz_stack_bwmorph.h"
#include "tz_dimage_lib.h"
#include "tz_darray.h"
#include "tz_u8array.h"
#include "tz_stack_objlabel.h"
#include "tz_voxel_linked_list.h"
#include "tz_voxel_graphics.h"
#include "tz_dmatrix.h"
#include "tz_stack_sampling.h"
#include "tz_stack_threshold.h"

INIT_EXCEPTION_MAIN(e)

int main(int argc, char* argv[])
{
  char neuron_name[100];
  if (argc == 1) {
    strcpy(neuron_name, "fly_neuron");
  } else {
    strcpy(neuron_name, argv[1]);
  }

  char file_path[100];
  sprintf(file_path, "../data/%s/mask.tif", neuron_name);

  Stack *mask = Read_Stack(file_path);

  sprintf(file_path, "../data/%s/seeds.pa", neuron_name);
  Pixel_Array *pa = Pixel_Array_Read(file_path);
  double *pa_array = (double *) pa->array;

  sprintf(file_path, "../data/%s/seed_offset.ar", neuron_name);
  int seed_number = pa->size;
  int *seed_offset = (int *) malloc(sizeof(int) * seed_number);
  iarray_read(file_path, seed_offset, &seed_number);

  int max_idx;
  double max_dist = darray_max(pa_array, pa->size, &max_idx);
  max_idx = seed_offset[max_idx];

  Stack_Label_Object_Dist_N(mask, NULL, max_idx, 1, 2, max_dist * 2.0, 26);

  Stack_Threshold_Binarize(mask, 1);

  sprintf(file_path, "../data/%s/blobmask.tif", neuron_name);
  Write_Stack(file_path, mask);

  free(seed_offset);
  Kill_Stack(mask);

  return 0;
}


/**
 * @file bblas_ztesting.c
 *
 * @brief BBLAS testing  for double _Complex routines.
 *
 * 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
 * @precisions normal z -> c d s
 **/
#endif

#include "bblas_common.h"
#if defined(BBLAS_WITH_MKL)
#include <mkl_lapacke.h>
#else
#include <lapacke.h>
#endif
#include <cblas.h>

#define COMPLEX

/**
 * Initialize test parameters to their default values.
 **/

void bblas_zinit_config (bblas_ztest_t *test)
{
    test->gen_uplo       =1;
    test->gen_transA     =1;
    test->gen_transB     =1;
    test->gen_trans      =1;
    test->gen_side       =1;
    test->gen_diag       =1;
    test->minM           = 0;
    test->minN           = 0;
    test->minK           = 0;
    test->maxM           = 0;
    test->maxN           = 0;
    test->maxK           = 0;
    test->minbatch_count = 1;
    test->maxbatch_count = 1;
    test->batch_opts     = 0;
    test->routine        = 1;
    test->nb_test        = 1;
    test->set_error      = 0;
    test->global_error   = 0;
    test->faulty_iter    = 0;
    test->mkl_sequential = 0;
    test->new_accuracy   = 1;
}

/**
 * Set the values of all the BBLAS parameters inside the test structure.
 **/

void bblas_zsettest(bblas_ztest_t *test)
{

    enum BBLAS_ROUTINE routine = test->routine;

    /*
     * Set the value of batch count
     */
    bblas_zset_batch_count(test);

    /*
     * Allocate memory and set values for uplo
     */
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	(routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K)||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	bblas_zsetuplo(test);
    }

    /*
     * Allocate memory and set values for transA
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_TRMM) ||
	(routine == BBLAS_TRSM))
    {
	bblas_zsettransA(test);
    }

    /*
     * Allocate memory and set values for transB
     */
    if (routine == BBLAS_GEMM)
    {
	bblas_zsettransB(test);
    }

    /*
     * Allocate memory and set values for trans
     */
    if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
    {
	bblas_zsettrans(test);
    }

    /*
     * Allocate memory and set values for side
     */
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	bblas_zsetside(test);
    }

    /*
     * Allocate memory and set values for diag
     */
    if  ((routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	bblas_zsetdiag(test);
    }

    /*
     * Allocate memory and set values for  M
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_TRMM) ||
	(routine == BBLAS_TRSM))
    {
	bblas_zsetM(test);
    }

    /*
     * Allocate memory and set values for N, all routines
     */
    bblas_zsetN(test);

    /*
     * Allocate memory and set values for K
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYRK)  ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K))
    {
	bblas_zsetK(test);
    }

    /*
     * Allocate memory and set values for lda, all routines
     */
    bblas_zsetlda(test);

    /*
     * Allocate memory and set values for ldb, all routines
     */
    if ((routine == BBLAS_GEMM)  || (routine == BBLAS_SYMM)  ||
	(routine == BBLAS_HEMM)  || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K) || (routine == BBLAS_TRMM)  ||
	(routine == BBLAS_TRSM))
    {
	bblas_zsetldb(test);
    }

    /*
     * Allocate memory and set values for ldc, all routines
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	(routine == BBLAS_HER2K))
    {
	bblas_zsetldc(test);
    }

    /*
     * Allocate memory and set values for alpha, all routines
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK)  ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K)||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	bblas_zsetalpha(test);
    }

    /*
     * Allocate memory and set values for alpha
     */
    if (routine == BBLAS_HERK)
    {
	bblas_zsetalpha_herk(test);
    }

    /*
     * Allocate memory and set values for beta
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
        (routine == BBLAS_SYR2K))
    {
	bblas_zsetbeta(test);
    }

    /*
     * Allocate memory and set values for beta_herk
     */
    if ((routine == BBLAS_HERK) || (routine == BBLAS_HER2K))
    {
	bblas_zsetbeta_herk(test);
    }

    /*
     * Allocate memory and set values for arrayA
     */
    bblas_zsetarrayA(test);

    /*
     * Allocate memory and set values for arrayB
     */
    if ((routine == BBLAS_GEMM)  || (routine == BBLAS_SYMM)  ||
	(routine == BBLAS_HEMM)  || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K) || (routine == BBLAS_TRMM)  ||
	(routine == BBLAS_TRSM))
    {
	bblas_zsetarrayB(test);
    }

    /*
     * Allocate memory and set values for arrayC
     */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	(routine == BBLAS_HER2K))
    {
	bblas_zsetarrayC(test);
    }

    /* Memory allocation for result and error variables */
    bblas_zmalloc_result_error(test);
}

/**
 * Allocate memory and set the values of uplo
 **/

void bblas_zsetuplo(bblas_ztest_t *test)
{
    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "uplo";
    int random_number;
    /*Initialize random number generation */
    srand ( time(NULL) ) ;

    /*Memory allocation */
    test->uplo =  (enum BBLAS_UPLO*) malloc(nb_data*sizeof(enum BBLAS_UPLO));

    /*Malloc checking */
    bblas_malloc_check(test->uplo, ptr_name);

    /*set UPLO values */
    switch (test->gen_uplo)
    {
	case UPLO_LOWER:

	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		test->uplo[batch_iter] = BblasLower;
	    }
	    break;

	case UPLO_UPPER:

	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		test->uplo[batch_iter] = BblasUpper;
	    }
	    break;

	default:
	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		/*Generate a random number */
		random_number = rand() % 100;

		if (random_number < 50 )
		{
		    test->uplo[batch_iter] = BblasLower;

		}else
		{
		    test->uplo[batch_iter] = BblasUpper;
		}
		break;
	    }
    }
}

/**
 * Allocate memory and set the values of transA
 **/

void bblas_zsettransA(bblas_ztest_t *test)
{
    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "transA";
    int random_number;

    /*Initialize random number generation */
    srand ( time(NULL) ) ;

    /*Memory allocation */
    test->transA =  (enum BBLAS_TRANS*) malloc(nb_data*sizeof(enum BBLAS_TRANS));

    /*Malloc checking */
    bblas_malloc_check(test->transA, ptr_name);

    /*set transA */
    switch (test->gen_transA)
    {
	case NO_TRANS:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->transA[batch_iter] = BblasNoTrans;
	    }
	    break;

	case TRANS:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->transA[batch_iter] = BblasTrans;
	    }
	    break;

	case CONJ:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->transA[batch_iter] = BblasConjTrans;
	    }
	    break;

	default:
	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		/*Generate a random number */
		random_number = rand() % 100;

		if (random_number < 50)
		{
		    test->transA[batch_iter] = BblasNoTrans;

		}else if (random_number < 80)
		{
		    test->transA[batch_iter] = BblasTrans;
		}else
		{
		    test->transA[batch_iter] = BblasConjTrans;
		}
	    }
	    break;
    }
}

/**
 * Allocate memory and set the values of transB
 **/

void bblas_zsettransB(bblas_ztest_t *test)
{
    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "transB";
    int random_number;

    /*Initialize random number generation */
    srand ( time(NULL) ) ;

    /*Memory allocation for transB */
    test->transB =  (enum BBLAS_TRANS*) malloc(nb_data*sizeof(enum BBLAS_TRANS));

    /*checking memory allocation */
    bblas_malloc_check(test->transB, ptr_name);

    /*set transB */
    switch (test->gen_transB)
    {
	case NO_TRANS:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->transB[batch_iter] = BblasNoTrans;
	    }
	    break;

	case TRANS:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->transB[batch_iter] = BblasTrans;
	    }
	    break;

	case CONJ:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->transB[batch_iter] = BblasConjTrans;
	    }
	    break;

	default:
	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		/*Generate a random number */
		random_number = rand() % 100;

		if (random_number < 50)
		{
		    test->transB[batch_iter] = BblasNoTrans;

		}else if (random_number < 80)
		{
		    test->transB[batch_iter] = BblasTrans;
		}else
		{
		    test->transB[batch_iter] = BblasConjTrans;
		}
	    }
	    break;
    }

}

/**
 * Allocate memory and set the values of trans
 **/

void bblas_zsettrans(bblas_ztest_t *test)
{
    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "trans";
    int random_number;

    /*Initialize random number generation */
    srand ( time(NULL) ) ;

    /*Memory allocation for trans */
    test->trans =  (enum BBLAS_TRANS*) malloc(nb_data*sizeof(enum BBLAS_TRANS));

    /*checking memory allocation */
    bblas_malloc_check(test->trans, ptr_name);

    /*Set the values of trans */
    switch (test->gen_trans)
    {
	case NO_TRANS:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->trans[batch_iter] = BblasNoTrans;
	    }
	    break;

	case TRANS:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->trans[batch_iter] = BblasTrans;
	    }
	    break;

	case CONJ:

	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		test->trans[batch_iter] = BblasConjTrans;
	    }
	    break;

	default:
	    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	    {
		/*Generate a random number */
		random_number = rand() % 100;

		if (random_number < 50)
		{
		    test->trans[batch_iter] = BblasNoTrans;

		}else if (random_number < 80)
		{
		    test->trans[batch_iter] = BblasTrans;
		}else
		{
		    test->trans[batch_iter] = BblasConjTrans;
		}
	    }
	    break;
    }
}

/**
 * Allocate memory and set the values of side
 **/

void bblas_zsetside(bblas_ztest_t *test)
{

    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "side";
    int random_number;

    /*Initialize random number generation */
    srand ( time(NULL) ) ;

    /*Memory allocation for side */
    test->side =  (enum BBLAS_SIDE*) malloc(nb_data*sizeof(enum BBLAS_SIDE));

    /*checking memory allocation */
    bblas_malloc_check(test->side, ptr_name);

    /*Set the values of side */
    switch (test->gen_side)
    {
	case SIDE_LEFT:

	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		test->side[batch_iter] = BblasLeft;
	    }
	    break;

	case SIDE_RIGHT:

	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		test->side[batch_iter] = BblasRight;
	    }
	    break;

	default:
	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		/*Generate a random number */
		random_number = rand() % 100;

		if (random_number < 50 )
		{
		    test->side[batch_iter] = BblasLeft;

		}else
		{
		    test->side[batch_iter] = BblasRight;
		}
		break;
	    }
    }
}

/**
 * Allocate memory and set the values of diag
 **/

void bblas_zsetdiag(bblas_ztest_t *test)
{

    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "diag";
    int random_number;

    /*Initialize random number generation */
    srand ( time(NULL) ) ;

    /*Memory allocation for diag */
    test->diag =  (enum BBLAS_DIAG*) malloc(nb_data*sizeof(enum BBLAS_DIAG));

    /*checking memory allocation */
    bblas_malloc_check(test->diag, ptr_name);

    /*Set the values of diag */
    switch (test->gen_diag)
    {
	case DIAG_NO_U:

	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		test->diag[batch_iter] = BblasNonUnit;
	    }
	    break;

	case DIAG_U:

	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		test->diag[batch_iter] = BblasUnit;
	    }
	    break;

	default:
	    for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	    {
		/*Generate a random number */
		random_number = rand() % 100;

		if (random_number < 50 )
		{
		    test->diag[batch_iter] = BblasNonUnit;

		}else
		{
		    test->diag[batch_iter] = BblasUnit;
		}
		break;
	    }
    }
}

/**
 * Allocate memory and set the values of M
 **/

void bblas_zsetM(bblas_ztest_t *test)
{

    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "M";

    /*Memory allocation for M */
    test->M = (int*) malloc(nb_data*sizeof(int));

    /*checking memory allocation */
    bblas_malloc_check(test->M, ptr_name);

    /*Set the values of M */
    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
    {
	test->M[batch_iter] = irandRange(test->minM, test->maxM);
    }
}

/**
 * Allocate memory and set the values of N
 **/

void bblas_zsetN(bblas_ztest_t *test)
{

    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "N";

    /*Memory allocation for N */
    test->N = (int*) malloc(nb_data*sizeof(int));

    /*checking memory allocation */
    bblas_malloc_check(test->N, ptr_name);

    /*Set the values of N */
    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
    {
	test->N[batch_iter] = irandRange(test->minN, test->maxN);
    }
}

/**
 * Allocate memory and set the values of K
 **/

void bblas_zsetK(bblas_ztest_t *test)
{
    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "K";

    /*Memory allocation for K */
    test->K = (int*) malloc(nb_data*sizeof(int));

    /*checking memory allocation */
    bblas_malloc_check(test->K, ptr_name);

    /*Set the values of K */
    for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
    {
	test->K[batch_iter] = irandRange(test->minK, test->maxK);
    }
}

/**
 * Allocate memory and set the values of lda
 **/

void bblas_zsetlda(bblas_ztest_t *test)
{
    int batch_iter;
    int nb_data                 = bblas_znbdata(test);
    int routine                 = test->routine;
    char ptr_name[NAME_LENGTH]  = "lda";

    /*Memory allocation for lda */
    test->lda = (int*) malloc(nb_data*sizeof(int));

    /*checking memory allocation */
    bblas_malloc_check(test->lda, ptr_name);

    /*LDA for GEMM */
    if (routine == BBLAS_GEMM)
    {
	for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	{
	    if (test->transA[batch_iter] == BblasNoTrans)
	    {
		test->lda[batch_iter] = test->M[batch_iter];
	    }else
	    {
		test->lda[batch_iter] =test->K[batch_iter] ;
	    }
	}
    }

    /*LDA for SYMM, HEMM, TRMM AND TRSM */
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	{
	    if (test->side[batch_iter] == BblasLeft)
	    {
		test->lda[batch_iter] = test->M[batch_iter];
	    }else
	    {
		test->lda[batch_iter] =test->N[batch_iter] ;
	    }
	}
    }

    /*LDA for SYRK, HERK, SYR2K, HER2K */
    if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
    {
	for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	{
	    if (test->trans[batch_iter] == BblasNoTrans)
	    {
		test->lda[batch_iter] = test->N[batch_iter];
	    }else
	    {
		test->lda[batch_iter] =test->K[batch_iter] ;
	    }
	}
    }
}

/**
 * Allocate memory and set the values of ldb
 **/

void bblas_zsetldb(bblas_ztest_t *test)
{
    int batch_iter;
    int routine                 = test->routine;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "ldb";

    /*Memory allocation for ldb */
    test->ldb = (int*) malloc(nb_data*sizeof(int));

    /*checking memory allocation */
    bblas_malloc_check(test->ldb, ptr_name);

    /*LDB for GEMM */
    if ((routine == BBLAS_GEMM) )
    {
	for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	{
	    if (test->transB[batch_iter] == BblasNoTrans)
	    {
		test->ldb[batch_iter] = test->K[batch_iter];
	    }else
	    {
		test->ldb[batch_iter] =test->N[batch_iter] ;
	    }
	}
    }

    /*LDB SYMM, HEMM, TRMM AND TRSM */
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	{
	    test->ldb[batch_iter] = test->M[batch_iter];
	}
    }

    /*LDB for SYR2K, HER2K */
    if ((routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
    {
	for( batch_iter =0; batch_iter < nb_data; batch_iter++)
	{
	    if (test->trans[batch_iter] == BblasNoTrans)
	    {
		test->ldb[batch_iter] = test->N[batch_iter];
	    }else
	    {
		test->ldb[batch_iter] =test->K[batch_iter] ;
	    }
	}
    }
}

/**
 * Allocate memory and set the values of ldc
 **/

void bblas_zsetldc(bblas_ztest_t *test)
{
    int batch_iter;
    int routine                 = test->routine;
    int nb_data                 = bblas_znbdata(test);
    char ptr_name[NAME_LENGTH]  = "ldc";

    /*Memory allocation for ldc */
    test->ldc = (int*) malloc(nb_data*sizeof(int));

    /*checking memory allocation */
    bblas_malloc_check(test->ldc, ptr_name);

    /*LDC for GEMM */
    if (routine == BBLAS_GEMM)
    {
	for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	{
	    test->ldc[batch_iter] = test->M[batch_iter];
	}
    }

    /*LDC for SYMM, HEMM */
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM))
    {
	for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	{
	    test->ldc[batch_iter] = test->M[batch_iter];
	}
    }

    /*LDC for SYRK, HERK, SYR2K, HER2K */
    if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
    {
	for( batch_iter =0; batch_iter < nb_data ; batch_iter++)
	{
	    test->ldc[batch_iter] = test->N[batch_iter];
	}
    }
}

/**
 * Allocate memory and set the values of alpha
 **/

void bblas_zsetalpha(bblas_ztest_t *test)
{
    int batch_iter;
    int batch_count             = test->batch_count;
    char ptr_name[NAME_LENGTH]  = "alpha";

    /*Memory allocation for alpha */
    test->alpha = (BBLAS_Complex64_t*) malloc(batch_count*sizeof(BBLAS_Complex64_t));

    /*checking memory allocation */
    bblas_malloc_check(test->alpha, ptr_name);

    /* Set value of alpha */
    for( batch_iter =0; batch_iter < batch_count; batch_iter++)
    {
	test->alpha[batch_iter] = ((BBLAS_Complex64_t)rand()/(BBLAS_Complex64_t)RAND_MAX);
    }
}

/**
 * Allocate memory and set the values of alpha specifically for herk.
 **/

void bblas_zsetalpha_herk(bblas_ztest_t *test)
{

    int batch_iter;
    int batch_count             = test->batch_count;
    char ptr_name[NAME_LENGTH]  = "alpha_herk";

    /*Memory allocation for alpha_herk */
    test->alpha_herk = (double*) malloc(batch_count*sizeof(double));

    /*checking memory allocation */
    bblas_malloc_check(test->alpha_herk, ptr_name);

    /* Set value of alpha_herk */
    for( batch_iter =0; batch_iter < batch_count; batch_iter++)
    {
	test->alpha_herk[batch_iter] = ((double)rand()/(double)RAND_MAX);
    }
}

/**
 * Allocate memory and set the values of beta.
 **/

void bblas_zsetbeta(bblas_ztest_t *test)
{
    int batch_iter;
    int batch_count             = test->batch_count;
    char ptr_name[NAME_LENGTH]  = "beta";

    /*Memory allocation for beta */
    test->beta = (BBLAS_Complex64_t*) malloc(batch_count*sizeof(BBLAS_Complex64_t));

    /*checking memory allocation */
    bblas_malloc_check(test->beta, ptr_name);

    /* Set value of beta */
    for( batch_iter =0; batch_iter < batch_count; batch_iter++)
    {
	test->beta[batch_iter] = ((BBLAS_Complex64_t)rand()/(BBLAS_Complex64_t)RAND_MAX);
    }
}

/**
 * Allocate memory and set the values of beta specifically for herk.
 **/

void bblas_zsetbeta_herk(bblas_ztest_t *test)
{
     int batch_iter;
     int batch_count             = test->batch_count;
     char ptr_name[NAME_LENGTH]  = "beta_herk";

     /*Memory allocation for beta_herk */
     test->beta_herk = (double*) malloc(batch_count*sizeof(double));

     /*checking memory allocation */
     bblas_malloc_check(test->beta_herk, ptr_name);

     /* Set value of beta_herk */
     for( batch_iter =0; batch_iter < batch_count; batch_iter++)
     {
	 test->beta_herk[batch_iter] = ((double)rand()/(double)RAND_MAX);
     }
}

/**
 * Allocate memory and set the values of arrayA.
 **/

void bblas_zsetarrayA(bblas_ztest_t *test)
{
    int batch_iter, nb_row, nb_col;
     int first_index             = 0;
     int routine                 = test->routine;
     int batch_count             = test->batch_count;
     char ptr_name[NAME_LENGTH]  = "arrayA";

     int IONE     = 1;
     int ISEED[4] ={0,0,0,1};

     /*Memory allocation for **arrayA  */
     test->arrayA = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
     bblas_malloc_check(test->arrayA, ptr_name);

     if( test->batch_opts == BBLAS_VARIABLE )
     {
	 for( batch_iter =0; batch_iter < batch_count; batch_iter++)
	 {
	     nb_row = test->lda[batch_iter];

	     /* nb_col  for GEMM */
	     if (routine == BBLAS_GEMM)
	     {
		 if (test->transA[batch_iter] == BblasNoTrans)
		 {
		     nb_col =test->K[batch_iter];
		 }else
		 {
		     nb_col =test->M[batch_iter];
		 }
	     }

	     /*  nb_col  SYMM, HEMM, TRMM AND TRSM */
	     if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
		 (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	     {
		 if(test->side[batch_iter] == BblasLeft )
		 {
		     nb_col = test->M[batch_iter];
		 }else
		 {
		     nb_col = test->N[batch_iter];
		 }
	     }

	     /* nb_col for SYRK, HERK, SYR2K, HER2K */
	     if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
		 (routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
	     {
		 if (test->trans[batch_iter] == BblasNoTrans)
		 {
		     nb_col = test->K[batch_iter];
		 }else
		 {
		     nb_col = test->N[batch_iter];
		 }
	     }

	     /*Matrix filling */
	     test->arrayA[batch_iter] = (BBLAS_Complex64_t *) malloc(nb_row*nb_col* sizeof(BBLAS_Complex64_t ));
	     bblas_malloc_check(test->arrayA[batch_iter], ptr_name);

#if defined(BBLAS_WITH_MKL)
	     LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, (MKL_Complex16*) test->arrayA[batch_iter]);
#else
		 LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, test->arrayA[batch_iter]);
#endif

	     if( (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	       {
		 for(int i=0; i<max(nb_row,nb_col); i++)
		   {
		     test->arrayA[batch_iter][nb_col*i+i] = test->arrayA[batch_iter][nb_col*i+i] + 1.0;
		   }
	       }

	     if(routine == BBLAS_HEMM )
	       {
		 for(int i=0; i< nb_row; i++)
		   {
		     test->arrayA[batch_iter][nb_col*i+i] = creal(test->arrayA[batch_iter][nb_col*i+i]);
		   }
	       }
	 }

     }else if( test->batch_opts == BBLAS_FIXED )
     {
	 nb_row = test->lda[first_index];

	 /* nb_col  for GEMM */
	 if (routine == BBLAS_GEMM)
	 {
	     if (test->transA[first_index] == BblasNoTrans)
	     {
		 nb_col =test->K[first_index];
	     }else
	     {
		 nb_col =test->M[first_index];
	     }
	 }

	 /*  nb_col  SYMM, HEMM, TRMM AND TRSM */
	 if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	     (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	 {
	     if(test->side[first_index] == BblasLeft )
	     {
		 nb_col = test->M[first_index];
	     }else
	     {
		 nb_col = test->N[first_index];
	     }
	 }

	 /* nb_col for SYRK, HERK, SYR2K, HER2K */
	 if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	     (routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
	 {
	     if (test->trans[first_index] == BblasNoTrans)
	     {
		 nb_col = test->K[first_index];
	     }else
	     {
		 nb_col = test->N[first_index];
	     }
	 }

	 /*Matrix filling */
	 for( batch_iter =0; batch_iter < batch_count; batch_iter++)
	 {
	     test->arrayA[batch_iter] = (BBLAS_Complex64_t *) malloc(nb_row*nb_col* sizeof(BBLAS_Complex64_t ));
	     bblas_malloc_check(test->arrayA[batch_iter], ptr_name);

#if defined(BBLAS_WITH_MKL)
	     LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, (MKL_Complex16*) test->arrayA[batch_iter]);
#else
		 LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, test->arrayA[batch_iter]);
#endif

	     if( (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	       {
		 for(int i=0; i<max(nb_row,nb_col); i++)
		   {
		     test->arrayA[batch_iter][nb_col*i+i] = test->arrayA[batch_iter][nb_col*i+i] + 1.0;
		   }
	       }

	     if(routine == BBLAS_HEMM )
	       {
		 for(int i=0; i<max(nb_row,nb_col); i++)
		   {
		     test->arrayA[batch_iter][nb_col*i+i] = creal(test->arrayA[batch_iter][nb_col*i+i]);
		   }
	       }
	 }
     }else
     {
	 bblas_error("bblas_ztesting.c", "wrong batch_opts value");
     }
}

/**
 * Allocate memory and set the values of arrayB.
 **/

void bblas_zsetarrayB(bblas_ztest_t *test)
{
    int batch_iter, nb_row, nb_col;
    int first_index             = 0;
    int routine                 = test->routine;
    int batch_count             = test->batch_count;
    char ptr_name[NAME_LENGTH]  = "arrayB";
    int max_work_size = max(test->maxK, max(test->maxM, test->maxN));
    int IONE     = 1;
    int ISEED[4] ={0,0,0,1};

    /*Memory allocation for arrayB */
    test->arrayB = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
    bblas_malloc_check(test->arrayB, ptr_name);

    test->Binitnorm = (double *)malloc(batch_count*sizeof(double));
    bblas_malloc_check(test->Binitnorm, "Binitnorm");

    double *work = (double *)malloc(max_work_size*sizeof(double));
    bblas_malloc_check(work, "work");

    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    nb_row =test->ldb[batch_iter];

	    /* nb_col  for GEMM */
	    if (routine == BBLAS_GEMM)
	    {
		if (test->transB[batch_iter] == BblasNoTrans)
		{
		    nb_col = test->N[batch_iter];
		}else
		{
		    nb_col = test->K[batch_iter];
		}
	    }

	    /* nb_col for SYMM, HEMM, TRMM AND TRSM */
	    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
		(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	    {
		nb_col =test->N[batch_iter];
	    }

	    /* nb_col for SYR2K, HER2K */
	    if ((routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
	    {
		if (test->trans[batch_iter] == BblasNoTrans)
		{
		    nb_col = test->K[batch_iter];
		}else
		{
		    nb_col = test->N[batch_iter];
		}
	    }

	    test->arrayB[batch_iter] = (BBLAS_Complex64_t *) malloc(nb_row*nb_col* sizeof(BBLAS_Complex64_t ));
	    bblas_malloc_check(test->arrayB[batch_iter], ptr_name);

#if defined(BBLAS_WITH_MKL)
	    LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, (MKL_Complex16*) test->arrayB[batch_iter]);
#else
		LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, test->arrayB[batch_iter]);
#endif

	    /*Compute the infinity norm of B  */
	    if( (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	    {
#if defined(BBLAS_WITH_MKL)
		test->Binitnorm[batch_iter] = (BBLAS_Complex64_t) LAPACKE_zlange_work(LAPACK_COL_MAJOR,
								  'I', nb_row, nb_col,
								  (MKL_Complex16*) test->arrayB[batch_iter],
								  test->ldb[batch_iter], work);
#else
		test->Binitnorm[batch_iter] = LAPACKE_zlange_work(LAPACK_COL_MAJOR,
								  'I', nb_row, nb_col,
								  test->arrayB[batch_iter],
								  test->ldb[batch_iter], work);
#endif
	    }
	}

    }else if( test->batch_opts == BBLAS_FIXED )
    {
	nb_row =test->ldb[first_index];

	/* nb_col  for GEMM */
	if (routine == BBLAS_GEMM)
	{
	    if (test->transB[first_index] == BblasNoTrans)
	    {
		nb_col = test->N[first_index];
	    }else
	    {
		nb_col = test->K[first_index];
	    }
	}

	/* nb_col for SYMM, HEMM, TRMM AND TRSM */
	if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	    (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	{
	    nb_col =test->N[first_index];
	}

	/* nb_col for SYR2K, HER2K */
	if ((routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
	{
	    if (test->trans[first_index] == BblasNoTrans)
	    {
		nb_col = test->K[first_index];
	    }else
	    {
		nb_col = test->N[first_index];
	    }
	}
	/*Matrix filling */
	for( batch_iter =0; batch_iter < batch_count; batch_iter++)
	{
	     test->arrayB[batch_iter] = (BBLAS_Complex64_t *) malloc(nb_row*nb_col* sizeof(BBLAS_Complex64_t ));
#if defined(BBLAS_WITH_MKL)
	     LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, (MKL_Complex16*) test->arrayB[batch_iter]);
#else
		 LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, test->arrayB[batch_iter]);
#endif

	     /*Compute the infinity norm of B  */
	     if( (routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
	     {
#if defined(BBLAS_WITH_MKL)
		 test->Binitnorm[batch_iter] = (BBLAS_Complex64_t) LAPACKE_zlange_work(LAPACK_COL_MAJOR,
								   'I', nb_row, nb_col,
								   (MKL_Complex16*) test->arrayB[batch_iter],
								   test->ldb[first_index], work);
#else
		 test->Binitnorm[batch_iter] = LAPACKE_zlange_work(LAPACK_COL_MAJOR,
								   'I', nb_row, nb_col,
								   test->arrayB[batch_iter],
								   test->ldb[first_index], work);
#endif
	     }
	 }
    }else
    {
	bblas_error("bblas_ztesting.c", "wrong batch_opts value");
    }
    /*Free work */
    free(work);
}

/**
 * Allocate memory and set the values of arrayC.
 **/

void bblas_zsetarrayC(bblas_ztest_t *test)
{
    int batch_iter, nb_row, nb_col;
    int first_index             = 0;
    int batch_count             = test->batch_count;
    int max_work_size = max(test->maxK, max(test->maxM, test->maxN));
    char ptr_name[NAME_LENGTH]  = "arrayC";

    int IONE     = 1;
    int ISEED[4] ={0,0,0,1};

    test->arrayC = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
    bblas_malloc_check(test->arrayC, ptr_name);

    test->Cinitnorm = (double *)malloc(batch_count*sizeof(double));
    bblas_malloc_check(test->Cinitnorm, "Cinitnorm");

    double *work = (double *)malloc(max_work_size*sizeof(double));
    bblas_malloc_check(work, "work");

    if( test->batch_opts == BBLAS_VARIABLE )
    {
	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    nb_row =test->ldc[batch_iter];
	    nb_col =test->N[batch_iter];

	    test->arrayC[batch_iter] = (BBLAS_Complex64_t *) malloc(nb_row*nb_col* sizeof(BBLAS_Complex64_t ));
	    bblas_malloc_check(test->arrayC[batch_iter], ptr_name);
#if defined(BBLAS_WITH_MKL)
	    LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, (MKL_Complex16*) test->arrayC[batch_iter]);
#else
		LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, test->arrayC[batch_iter]);
#endif

	    if( (test->routine == BBLAS_HERK) || (test->routine == BBLAS_HER2K))
	      {
		for(int i=0; i<max(nb_row,nb_col); i++)
		  {
		    test->arrayC[batch_iter][nb_col*i+i] = creal(test->arrayC[batch_iter][nb_col*i+i]);
		  }
	      }

	    /*Compuptation of the norm of C */
#if defined(BBLAS_WITH_MKL)
	    test->Cinitnorm[batch_iter] = (BBLAS_Complex64_t) LAPACKE_zlange_work(LAPACK_COL_MAJOR,
								  'I', nb_row, nb_col, (MKL_Complex16*) test->arrayC[batch_iter],
								  test->ldc[batch_iter], work);
#else
		test->Cinitnorm[batch_iter] = LAPACKE_zlange_work(LAPACK_COL_MAJOR,
							      'I', nb_row, nb_col, test->arrayC[batch_iter],
								  test->ldc[batch_iter], work);
#endif
	}

    }else if( test->batch_opts == BBLAS_FIXED )
    {
	nb_row =test->ldc[first_index];
	nb_col =test->N[first_index];

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    test->arrayC[batch_iter] = (BBLAS_Complex64_t *) malloc(nb_row*nb_col* sizeof(BBLAS_Complex64_t ));
	    bblas_malloc_check(test->arrayC[batch_iter], ptr_name);

#if defined(BBLAS_WITH_MKL)
	    LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, (MKL_Complex16*) test->arrayC[batch_iter]);
#else
		LAPACKE_zlarnv_work(IONE, ISEED, nb_row*nb_col, test->arrayC[batch_iter]);
#endif

	    if( (test->routine == BBLAS_HERK) || (test->routine == BBLAS_HER2K))
	      {
		for(int i=0; i<max(nb_row,nb_col); i++)
		  {
		    test->arrayC[batch_iter][nb_col*i+i] = creal(test->arrayC[batch_iter][nb_col*i+i]);
		  }
	      }

	    /*Compuptation of the norm of C */
#if defined(BBLAS_WITH_MKL)
	    test->Cinitnorm[batch_iter] = (BBLAS_Complex64_t) LAPACKE_zlange_work(LAPACK_COL_MAJOR,
									  'I', nb_row, nb_col,
									  (MKL_Complex16*) test->arrayC[batch_iter],
									  test->ldc[first_index], work);
		#else
		test->Cinitnorm[batch_iter] = LAPACKE_zlange_work(LAPACK_COL_MAJOR,
							      'I',nb_row, nb_col, test->arrayC[batch_iter],
								  test->ldc[first_index], work);
		#endif
	}
    } else
    {
	bblas_error("bblas_ztesting.c", "wrong batch_opts value");
    }

    /*Free work */
    free(work);
}

/**
 * Allocate memory for error checking.
 **/

void bblas_zmalloc_result_error(bblas_ztest_t *test)
{

    int batch_count             = test->batch_count;

    /*Memory for error computation  */
    switch(test->target)
    {
	case BBLAS_MKL:
	    test->mkl_result  = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
	    bblas_malloc_check(test->mkl_result, "mkl_result");

	    test->mkl_error = (double*) malloc(batch_count*sizeof(double));
	    bblas_malloc_check(test->mkl_error, "mkl_error");
	    break;

	case BBLAS_CUBLAS:
	case BBLAS_MAGMA:
	    test->device_result = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
	    bblas_malloc_check(test->device_result, "device_result");

	    test->device_error = (double*) malloc(batch_count*sizeof(double));
	    bblas_malloc_check(test->device_error, "device_error");
	    break;

	case BBLAS_OTHER:
	    test->other_result  = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
	    bblas_malloc_check(test->other_result, "other_result");

	    test->other_error = (double*) malloc(batch_count*sizeof(double));
	    bblas_malloc_check(test->other_error, "other_error");
	    break;

	case BBLAS_CUMKL:
	    test->mkl_result    = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
	    bblas_malloc_check(test->mkl_result, "mkl_result");

	    test->device_result = (BBLAS_Complex64_t**) malloc(batch_count*sizeof(BBLAS_Complex64_t*));
	    bblas_malloc_check(test->device_result, "device_result");

	    test->mkl_error = (double*) malloc(batch_count*sizeof(double));
	    bblas_malloc_check(test->mkl_error, "mkl_error");

	    test->device_error = (double*) malloc(batch_count*sizeof(double));
	    bblas_malloc_check(test->device_error, "device_error");
	    break;

	default:
	    printf("Memory alloation for error: Target no defined\n");
	    exit(EXIT_FAILURE);
    }

    /*Memory for info  */
    test->info = (int*) malloc(batch_count*sizeof(int));
}

/**
 * Allocate memory and copy the initial arrayC values.
 * This is required to take norms after the computation is complete.
 **/

void bblas_zcopy_Cinit(bblas_ztest_t *test, BBLAS_Complex64_t **C_copy)
{
    /*Local variables */
    enum BBLAS_ROUTINE routine  = test->routine;
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int ldc, N;

    if (!((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	  (routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	  (routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	  (routine == BBLAS_HER2K)))
    {
	printf("BBLAS FATAL ERROR: bblas_ztesting.c():\n");
	printf("\t bblas_zcopy_Cinit not defined for %s \n", bblas_getroutine(test->routine));
	exit(EXIT_FAILURE);
    }

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

	    /*Copy the matrix {C}_i */
	    cblas_zcopy (ldc*N, test->arrayC[batch_iter], 1, C_copy[batch_iter], 1);
	}

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

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    C_copy[batch_iter] = (BBLAS_Complex64_t *) malloc(ldc*N* sizeof(BBLAS_Complex64_t ));

	    /*Copy the matrix {C}_i */
	    cblas_zcopy (ldc*N, test->arrayC[batch_iter], 1,  C_copy[batch_iter], 1);
	}
    }else
    {
	bblas_error("bblas_ztesting.c", "wrong batch_opts value\n");
    }
}

/**
 * Allocate memory and copy the initial arrayB values.
 * This is required to take norms after the computation is complete.
 **/

void bblas_zcopy_Binit(bblas_ztest_t *test, BBLAS_Complex64_t **B_copy)
{
    /*Local variables */
    enum BBLAS_ROUTINE routine  = test->routine;
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int ldb, N;

    if (!((routine == BBLAS_TRMM) || (routine == BBLAS_TRSM)))
    {
	printf("BBLAS FATAL ERROR: bblas_ztesting.c():\n");
	printf("\t bblas_zcopy_Binit not defined for %s \n", bblas_getroutine(test->routine));
	exit(EXIT_FAILURE);
    }

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

	    B_copy[batch_iter] = (BBLAS_Complex64_t *) malloc(ldb*N* sizeof(BBLAS_Complex64_t ));

	    /*Copy the matrix {B}_i */
	    cblas_zcopy (ldb*N, test->arrayB[batch_iter], 1, B_copy[batch_iter], 1);
	}

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

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    B_copy[batch_iter] = (BBLAS_Complex64_t *) malloc(ldb*N* sizeof(BBLAS_Complex64_t ));

	    /*Copy the matrix {B}_i */
	    cblas_zcopy (ldb*N, test->arrayB[batch_iter], 1, B_copy[batch_iter], 1);
	}
    }else
    {
	bblas_error("bblas_ztesting.c", "wrong batch_opts value\n");
    }
}

/**
 * Compute the relative error of each batch operation.
 **/

void bblas_zcheck_Cfinal(bblas_ztest_t *test, BBLAS_Complex64_t **C_final)
{

    /*Local variables */
    enum BBLAS_ROUTINE routine  = test->routine;
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int ldc, N;

    /* Error calculation variables */
    double Cnorm, Error_norm;
    BBLAS_Complex64_t alpha =-1;

    /*Check if the call has been made by the correct routine */
    if (!((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	  (routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	  (routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	  (routine == BBLAS_HER2K)))
    {
	printf("BBLAS FATAL ERROR: bblas_ztesting.c():\n");
	printf("\t bblas_zcheck_Cfinal not defined for %s \n", bblas_getroutine(test->routine));
	exit(EXIT_FAILURE);
    }

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

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

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

	    /*Computation of the Frobenus norm of  {C}_batch_iter */
	    Cnorm = cblas_dznrm2(ldc*N, test->arrayC[batch_iter], 1);

	    /*Compute the error */
	    cblas_zaxpy (ldc*N, CBLAS_SADDR(alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

	    /*Compute  the norm assoicated  with the error */
	    Error_norm = cblas_dznrm2(ldc*N, C_diff[batch_iter], 1);

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

		case BBLAS_CUBLAS:
                case BBLAS_MAGMA:
  		    test->device_error[batch_iter] = Error_norm/Cnorm;
		    break;

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

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

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    /*Computation of the Frobenus norm of  {C}_batch_iter */
	    Cnorm = cblas_dznrm2(ldc*N, test->arrayC[batch_iter], 1);

	    /*Compute the error */
	    cblas_zaxpy (ldc*N, CBLAS_SADDR(alpha), C_final[batch_iter], 1, C_diff[batch_iter], 1);

	    /*Compute  the norm assoicated  with the error */
	    Error_norm = cblas_dznrm2(ldc*N, C_diff[batch_iter], 1);

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

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = Error_norm/Cnorm;
		    break;

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

    /*Free  C_diff */
    for(batch_iter=0; batch_iter < batch_count ; batch_iter++)
    {
	free(C_diff[batch_iter]);
    }
    free(C_diff);
}

/**
 * Compute the relative error of each batch operation.
 **/

void bblas_zcheck_Bfinal(bblas_ztest_t *test, BBLAS_Complex64_t **B_final)
{

    /*Local variables */
    enum BBLAS_ROUTINE routine  = test->routine;
    int batch_count = test->batch_count;
    int batch_iter, first_index = 0;
    int ldb, N;

    /* Error calculation variables */
    double Bnorm, Error_norm;
    BBLAS_Complex64_t alpha =-1;

    /*Check if the call has been made by the correct routine */
    if (!((routine == BBLAS_TRSM) || (routine == BBLAS_TRMM)))
    {
	printf("BBLAS FATAL ERROR: bblas_ztesting.c():\n");
	printf("\t bblas_zcheck_Bfinal not defined for %s \n", bblas_getroutine(test->routine));
	exit(EXIT_FAILURE);
    }

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

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

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

	    /*Computation of the Frobenus norm of  {B}_batch_iter */
	    Bnorm = cblas_dznrm2(ldb*N, test->arrayB[batch_iter], 1);

	    /*Compute the error */
	    cblas_zaxpy (ldb*N, CBLAS_SADDR(alpha), B_final[batch_iter], 1, B_diff[batch_iter], 1);

	    /*Compute  the norm assoicated  with the error */
	    Error_norm = cblas_dznrm2(ldb*N, B_diff[batch_iter], 1);

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

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = Error_norm/(Bnorm);
		    break;

		case BBLAS_OTHER:
		    test->other_error[batch_iter] = Error_norm/(Bnorm);
		    break;
		default:
		    printf("In bblas_zcheck_Bfinal(): Target no defined\n");
		    exit(EXIT_FAILURE);
	    }
	}

    }else  if( test->batch_opts == BBLAS_FIXED )
    {
	ldb = test->ldb[first_index];
	N = test->N[first_index];

	for( batch_iter =0; batch_iter < batch_count ; batch_iter++)
	{
	    /*Computation of the Frobenus norm of  {B}_batch_iter */
	    Bnorm = cblas_dznrm2(ldb*N, test->arrayB[batch_iter], 1);

	    /*Compute the error */
	    cblas_zaxpy (ldb*N, CBLAS_SADDR(alpha), B_final[batch_iter], 1, B_diff[batch_iter], 1);

	    /*Compute  the norm assoicated  with the error */
	    Error_norm = cblas_dznrm2(ldb*N, B_diff[batch_iter], 1);

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

		case BBLAS_CUBLAS:
		case BBLAS_MAGMA:
		    test->device_error[batch_iter] = Error_norm/(Bnorm);
		    break;

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

    /*Free  B_diff */
    for(batch_iter=0; batch_iter < batch_count ; batch_iter++)
    {
	free(B_diff[batch_iter]);
    }
    free(B_diff);
}

/**
 * Free the memory associated with the test structure.
 **/

void bblas_zfreetest( bblas_ztest_t *test )
{
    enum BBLAS_ROUTINE routine  = test->routine;
    /*Free uplo */
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	(routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K)||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	free(test->uplo);
    }

    /*Free TRANSA */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_TRMM) ||
	(routine == BBLAS_TRSM))
    {
	free( test->transA );
    }

    /*Free TRANSB */
    if (routine == BBLAS_GEMM)
    {
	free( test->transB );
    }

    /*Free TRANS */
    if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
    {
	free( test->trans );
    }

    /*Free SIDE*/
    if ((routine == BBLAS_SYMM) || (routine == BBLAS_HEMM) ||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	free( test->side );
    }

    /*Free DIAG*/
    if  ((routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	free(test->diag);
    }

    /* Free M  memory*/
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_TRMM) ||
	(routine == BBLAS_TRSM))
    {
	free( test->M );
    }

    /* Free N memory*/
    free( test->N );

    /* Free K memory*/
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYRK)  ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K))
    {
	free( test->K );
    }

    /*Free LDA memory */
    free( test->lda );

    /*Free LDB memory */
    if ((routine == BBLAS_GEMM)  || (routine == BBLAS_SYMM)  ||
	(routine == BBLAS_HEMM)  || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K) || (routine == BBLAS_TRMM)  ||
	(routine == BBLAS_TRSM))
    {
	free( test->ldb );
    }

    /*Free LDC memory */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	(routine == BBLAS_HER2K))
    {
	free( test->ldc );
    }

    /*Free ALPHA memory */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) ||(routine == BBLAS_SYRK)  ||
	(routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K)||
	(routine == BBLAS_TRMM) || (routine == BBLAS_TRSM))
    {
	free(test->alpha);

    }else if (routine == BBLAS_HERK)
    {
	free(test->alpha_herk);
    }

    /*Free ALPHA BETA memory */
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	(routine == BBLAS_SYR2K))
    {
	free( test->beta  );

    }else if ((routine == BBLAS_HERK) || (routine == BBLAS_HER2K))
    {
	free(test->beta_herk);
    }

    /*Free matrices {A}_i  */
    for ( int batch_iter = 0; batch_iter < test->batch_count; batch_iter++ )
    {
	free( test->arrayA[batch_iter] );
    }
    free( test->arrayA );

    /*Free matrices  {B}_i*/
    if ((routine == BBLAS_GEMM)  || (routine == BBLAS_SYMM)  ||
	(routine == BBLAS_HEMM)  || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K) || (routine == BBLAS_TRMM)  ||
	(routine == BBLAS_TRSM))
    {
	for ( int batch_iter = 0; batch_iter < test->batch_count; batch_iter++ )
	{
	    free( test->arrayB[batch_iter] );
	}
	free( test->arrayB );
    }

    /*Free matrices  {C}_i*/
    if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	(routine == BBLAS_HER2K))
    {
	for ( int batch_iter = 0; batch_iter < test->batch_count; batch_iter++ )
	{
	    free( test->arrayC[batch_iter] );
	}
	free( test->arrayC );
    }

    /*Free memory allocated for error computation */
    switch(test->target)
    {
	case BBLAS_MKL:
	    free(test->mkl_error);
	    //free(test->group_size);
	    for ( int batch_iter = 0; batch_iter < test->batch_count; batch_iter++ )
	    {
		free( test->mkl_result[batch_iter] );
	    }
	    free( test->mkl_result );

	    break;

	case BBLAS_CUBLAS:
	case BBLAS_MAGMA:
	    free(test->device_error);

	    for (int batch_iter =0; batch_iter < test->batch_count; batch_iter++)
	    {
		free(test->device_result[batch_iter]);
	    }

	    free(test->device_result);

	    break;

	case BBLAS_OTHER:

	    free(test->other_error);

	    for (int batch_iter =0; batch_iter < test->batch_count; batch_iter++)
	    {
		free(test->other_result[batch_iter]);
	    }
	    free(test->other_result);

	    break;

	case BBLAS_CUMKL:
	    free(test->mkl_error);
	    free(test->device_error);
	    free(test->group_size);

	    for (int batch_iter =0; batch_iter < test->batch_count; batch_iter++)
	    {
		free(test->device_result[batch_iter]);
		free(test->mkl_result[batch_iter]);
	    }
	    free(test->device_result);
	    free(test->mkl_result);

	    break;

	default:
	    printf("In bblas_zfreetest(): Target no defined\n");
	    exit(EXIT_FAILURE);
    }
    /*Free INFO */
    free( test->info );

    /*Free cuda memory */
    if( (test->target == BBLAS_CUBLAS) || (test->target == BBLAS_MAGMA))
    {
	bblas_zcudaFree(test);
    }

	if ((routine == BBLAS_GEMM)  || (routine == BBLAS_SYMM)  ||
	(routine == BBLAS_HEMM)  || (routine == BBLAS_SYR2K) ||
	(routine == BBLAS_HER2K) || (routine == BBLAS_TRMM)  ||
	(routine == BBLAS_TRSM))
	{
		free(test->Binitnorm);
    }

    /*Free memory allocated for norm computing */
	if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	(routine == BBLAS_HEMM) || (routine == BBLAS_SYRK) ||
	(routine == BBLAS_HERK) || (routine == BBLAS_SYR2K)||
	(routine == BBLAS_HER2K))
    {
		free(test->Cinitnorm);
    }
}

#undef COMPLEX


#ifndef __Q_INCS
#define __Q_INCS
#include <alloca.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <float.h>
// #include <gsl/gsl_vector.h>
// #include <gsl/gsl_matrix.h>
// #include <gsl/gsl_blas.h>
// #include <gsl/gsl_linalg.h>
#include <inttypes.h>
#include <limits.h>
// TODO P4 do not think this is needed:#include <malloc.h>
#include <math.h>
#include <memory.h>
#include <omp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifndef MAC_OSX
// TODO P1 Is this needed?#include <sys/sysinfo.h>
#endif
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "q_macros.h"
#endif


#ifndef MATH_FNS_H
#define MATH_FNS_H

#ifdef USE_OPENBLAS
extern "C"
{
#include <cblas.h>
}
#endif

struct QuantizationParams
{
	float scale;
	uint8_t zero_point;
};

template<typename Dtype>
void cpu_gemm(bool transA, bool transB, uint64_t M, uint64_t N, uint64_t K, Dtype alpha, Dtype* A, Dtype* B, Dtype beta, Dtype* C);

template<typename Dtype>
void cpu_axpy(uint64_t N, Dtype alpha, Dtype* X, Dtype* Y, Dtype* C); // c = alpha * x + y

template<typename Dtype>
void cpu_axpby(uint64_t N, Dtype alpha, Dtype* X_ptr, Dtype beta, Dtype* Y_ptr, Dtype* C_ptr); // c = alpha * x + beta * y

template<typename Dtype>
void cpu_mul(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr); // c = a * b

template<typename Dtype>
void cpu_mul(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr, Dtype beta, Dtype* C_ptr); // c = beta * c + alpha * a * b

template<typename Dtype>
void cpu_mul(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha * a

template<typename Dtype>
void cpu_add(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha + a

template<typename Dtype>
void cpu_div(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr); // c += a / b

template<typename Dtype>
void cpu_div(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr, Dtype beta, Dtype* C_ptr); // c = (beta * c) +  (alpha * a / b)

template<typename Dtype>
void cpu_div_back(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr, Dtype* D_ptr); // special function for processing div_backward during backpropagation (calculates d += a * (-b) / (c * c))

template<typename Dtype>
void cpu_sig(uint64_t N, const Dtype* A_ptr, Dtype* B_ptr); // b = sig(a)

template<typename Dtype>
void cpu_tanh(uint64_t N, const Dtype* A_ptr, Dtype* B_ptr); // b = tanh(a)

template<typename Dtype>
void cpu_powx(uint64_t N, const Dtype* A_ptr, Dtype x, Dtype* B_ptr); // b = pow(a,x)

template<typename Dtype>
void cpu_copy(uint64_t N, Dtype* A_ptr, Dtype* B_ptr); // b = a

template<typename Dtype>
void cpu_max(const Dtype* src, Dtype* dst, uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_max_backward(Dtype* dst, const Dtype* src, const uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_sum(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_sum_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_mean(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_mean_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_var(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_var_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void cpu_std(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

template<typename Dtype>
void cpu_std_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const Dtype* std, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

template<typename Dtype>
void cpu_dropout(Dtype* dst, Dtype* src, unsigned int* mask, unsigned int threshold, Dtype scale, uint64_t len);

template<typename Dtype>
void cpu_sig_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void cpu_tanh_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

void cpu_transpose(float* src, float* dst, int dim_1, int dim_2,
	int stride_src_dim_1, int stride_src_dim_1_minus_1, int stride_src_dim_2, int stride_src_dim_2_minus_1,
	int stride_trn_dim_1, int stride_trn_dim_1_minus_1, int stride_trn_dim_2, int stride_trn_dim_2_minus_1, uint64_t numels);

void quantized_matmul(bool traspose_A, bool traspose_B, uint64_t M, uint64_t N, uint64_t K, uint8_t alpha, uint8_t* A, uint8_t* B, uint8_t beta, uint8_t* C, QuantizationParams* qparms, int* bias, int* workspace); // workspace must be at least (M + N) * sizeof(int) bytes

//-------------------------------------CUDA functions----------------------------------------------------------------

template<typename Dtype>
void gpu_sum(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B);

template<typename Dtype>
void gpu_sub(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B);

// C =  A * B
template<typename Dtype>
void gpu_mul(uint64_t N, Dtype* A_ptr, Dtype* B_ptr, Dtype* C_ptr);

// C = beta * C + A * B
// supports broadcast semantics
// use this when C dims >= both A and B dims (avoid atomics)
template<typename Dtype>
void gpu_mul(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, Dtype beta);

// C = C + A * B
// supports broadcast semantics
// use this when C dims < either A or B dims (uses atomics, but no support for uint8_t)
void gpu_mul(float* A, float* B, float* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, uint64_t height_C, uint64_t width_C);

template<typename Dtype>
void gpu_mul(uint64_t N, Dtype alpha, Dtype* A, Dtype* B, Dtype beta, Dtype* C); // c = beta * c + alpha * a * b

template<typename Dtype>
void gpu_mul(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha * a

// C =  A / B
// supports broadcast semantics
// use this when C dims >= both A and B dims (avoid atomics)
template<typename Dtype>
void gpu_div(Dtype* A, Dtype* B, Dtype* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B);

// C = C + A / B
// use this when C numels == A numels == B numels (no broadcast therefore fastest)
template<typename Dtype>
void gpu_div(uint64_t N, Dtype* A, Dtype* B, Dtype* C);

// C = A / B
// use this when C numels == A numels == B numels (no broadcast therefore fastest)
//template<typename Dtype>
//void gpu_div2(uint64_t N, Dtype* A, Dtype* B, Dtype* C);

// C = C + A / B
// supports broadcast semantics
// use this when C dims < either A or B dims (uses atomics, but no support for uint8_t)
void gpu_div(float* A, float* B, float* C, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, uint64_t height_C, uint64_t width_C);

// special function for processing div_backward during backpropagation (calculates d += a * (-b) / (c * c))
// supports broadcast semantics
// use this when D dims < A or B or C dims (uses atomics, but no support for uint8_t)
//template<typename Dtype>
void gpu_div_back(float* A, float* B, float* C, float* D, uint64_t height_A, uint64_t width_A, uint64_t height_B, uint64_t width_B, uint64_t height_C, uint64_t width_C, uint64_t height_D, uint64_t width_D);

template<typename Dtype>
void gpu_sum(Dtype* data, Dtype* sum, uint64_t len);

template<typename Dtype>
void gpu_nll(Dtype* input, Dtype* target, Dtype* loss, uint64_t len, uint64_t batches);

template<typename Dtype>
void gpu_scalar_mul(Dtype* A, Dtype* B, Dtype scalar, uint64_t len); // B = scaler * A

// C += alpha * A
// supports broadcast semantics
// use this when C dims < A dims (uses atomics, but no support for uint8_t)
void gpu_scalar_mul(float alpha, float* A, float* C, uint64_t height_A, uint64_t width_A, uint64_t height_C, uint64_t width_C);

template<typename Dtype>
void gpu_fill(Dtype* memory, uint64_t size, Dtype value);

template<typename Dtype>
void gpu_fill(Dtype* memory, uint64_t len, Dtype* value);

void gpu_sgd_step(float* weight_ptr, float* weight_grad_ptr, float* velocity_ptr, int64_t numels, float mo, float wd, float lr);

template<typename Dtype>
void gpu_max(const Dtype* src, Dtype* dst, uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_max_backward(Dtype* dst, const Dtype* src, const uint64_t* indices, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_powx(uint64_t N, const Dtype* A, Dtype x, Dtype* B); // b = pow(a,x)

template<typename Dtype>
void gpu_exp(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_log(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_sig(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_sig_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void gpu_tanh(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_tanh_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void gpu_sum(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_sum_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_add(uint64_t N, Dtype alpha, Dtype* A_ptr, Dtype* B_ptr); // b = alpha + a

template<typename Dtype>
void gpu_axpy(uint64_t N, Dtype alpha, Dtype* X_ptr, Dtype* Y_ptr, Dtype* C_ptr);

template<typename Dtype>
void gpu_axpby(uint64_t N, Dtype alpha, Dtype* X_ptr, Dtype beta, Dtype* Y_ptr, Dtype* C_ptr); // c = alpha * x + beta * y

template<typename Dtype>
void gpu_relu(Dtype* dst, Dtype* src, uint64_t len);

template<typename Dtype>
void gpu_relu_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t len);

template<typename Dtype>
void gpu_dropout(Dtype* dst, Dtype* src, unsigned int* mask, unsigned int threshold, Dtype scale, uint64_t len);

template<typename Dtype>
void gpu_transpose(Dtype* src, Dtype* dst, int dim_1, int dim_2,
	int stride_src_dim_1, int stride_src_dim_1_minus_1, int stride_src_dim_2, int stride_src_dim_2_minus_1,
	int stride_trn_dim_1, int stride_trn_dim_1_minus_1, int stride_trn_dim_2, int stride_trn_dim_2_minus_1, uint64_t numels);

template<typename Dtype>
void gpu_cat(Dtype* dest, Dtype* op1, Dtype* op2, uint64_t dest_stride_1, uint64_t dest_stride_2, uint64_t op1_stride, uint64_t op2_stride_1, uint64_t op2_stride_2, uint64_t dim_offset, uint64_t op1_numels, uint64_t op2_numels);

template<typename Dtype>
void gpu_cat_backward(Dtype* dest, Dtype* src, uint64_t dest_stride, uint64_t src_stride, uint64_t dest_numels);

template<typename Dtype>
void gpu_cat_backward(Dtype* dest, Dtype* src, uint64_t dest_stride_1, uint64_t dest_stride_2, uint64_t src_stride_1, uint64_t src_stride_2, uint64_t dim_offset, uint64_t op1_numels, uint64_t dest_numels);

template<typename Dtype>
void gpu_embedding(Dtype* dst, Dtype* wts, int* indices, uint64_t numels, uint64_t indices_per_batch, unsigned int embedding_dim);

template<typename Dtype>
void gpu_embedding_backward(Dtype* dst, Dtype* wts, int* indices, uint64_t numels, uint64_t indices_per_batch, unsigned int embedding_dim);

template<typename Dtype>
void gpu_sqrt(Dtype* dst, const Dtype* src, const uint64_t numels);

template<typename Dtype>
void gpu_sqrt_backward(Dtype* bottom, const Dtype* top, const Dtype* middle, const uint64_t numels);

template<typename Dtype>
void gpu_mean(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_mean_backward(Dtype* dst, const Dtype* src, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_var(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_var_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride);

template<typename Dtype>
void gpu_std(const Dtype* src, Dtype* dst, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

template<typename Dtype>
void gpu_std_backward(Dtype* dst, const Dtype* src, const Dtype* op1, const Dtype* std, const uint64_t numels, const uint64_t ratio, const uint64_t dim_size, const uint64_t stride, bool sample_mode = true);

#endif // MATH_FNS_H


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

#ifndef __GSL_FFT_HALFCOMPLEX_H__
#define __GSL_FFT_HALFCOMPLEX_H__

#if !defined( GSL_FUN )
#  if !defined( GSL_DLL )
#    define GSL_FUN extern
#  elif defined( BUILD_GSL_DLL )
#    define GSL_FUN extern __declspec(dllexport)
#  else
#    define GSL_FUN extern __declspec(dllimport)
#  endif
#endif

#include <stddef.h>

#include <gsl/gsl_math.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_fft.h>
#include <gsl/gsl_fft_real.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

GSL_FUN int gsl_fft_halfcomplex_radix2_backward (double data[], const size_t stride, const size_t n);
GSL_FUN int gsl_fft_halfcomplex_radix2_inverse (double data[], const size_t stride, const size_t n);
GSL_FUN int gsl_fft_halfcomplex_radix2_transform (double data[], const size_t stride, const size_t n);

typedef struct
  {
    size_t n;
    size_t nf;
    size_t factor[64];
    gsl_complex *twiddle[64];
    gsl_complex *trig;
  }
gsl_fft_halfcomplex_wavetable;

GSL_FUN gsl_fft_halfcomplex_wavetable * gsl_fft_halfcomplex_wavetable_alloc (size_t n);

GSL_FUN void
gsl_fft_halfcomplex_wavetable_free (gsl_fft_halfcomplex_wavetable * wavetable);

GSL_FUN int gsl_fft_halfcomplex_backward (double data[], const size_t stride, const size_t n,
                                  const gsl_fft_halfcomplex_wavetable * wavetable,
                                  gsl_fft_real_workspace * work);

GSL_FUN int gsl_fft_halfcomplex_inverse (double data[], const size_t stride, const size_t n,
                                 const gsl_fft_halfcomplex_wavetable * wavetable,
                                 gsl_fft_real_workspace * work);

GSL_FUN int gsl_fft_halfcomplex_transform (double data[], const size_t stride, const size_t n,
                                   const gsl_fft_halfcomplex_wavetable * wavetable,
                                   gsl_fft_real_workspace * work);

GSL_FUN int
gsl_fft_halfcomplex_unpack (const double halfcomplex_coefficient[],
                            double complex_coefficient[],
                            const size_t stride, const size_t n);

GSL_FUN int
gsl_fft_halfcomplex_radix2_unpack (const double halfcomplex_coefficient[],
                                   double complex_coefficient[],
                                   const size_t stride, const size_t n);

__END_DECLS

#endif /* __GSL_FFT_HALFCOMPLEX_H__ */


#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_math.h>

#include "allvars.h"
#include "proto.h"

#ifdef CR_DIFFUSION_GREEN
#include "cosmic_rays.h"

void compute_diff_weights(int target, int mode);
void scatter_diffusion(int target, int mode);

#define m_p (PROTONMASS * All.HubbleParam / All.UnitMass_in_g)

void greenf_diffusion(void)
{
  int *noffset, *nbuffer, *nsend, *nsend_local, *numlist, *ndonelist;
  int i, j, n;
  int ndone;
  long long ntot, ntotleft;
  int maxfill, source;
  int level, ngrp, sendTask, recvTask;
  int place, nexport;
  double cr_efac_i, kappa_egy;
  double kappa, a3inv, egysum, egytot, egytot_before;
  double meanKineticEnergy, qmeanKin;
  double CR_q_i;
  MPI_Status status;

  if(ThisTask == 0)
    {
      printf("Doing diffusion step with Green function method\n");
      fflush(stdout);
    }

  for(CRpop = 0; CRpop < NUMCRPOP; CRpop++)
    {

      noffset = mymalloc(sizeof(int) * NTask);	/* offsets of bunches in common list */
      nbuffer = mymalloc(sizeof(int) * NTask);
      nsend_local = mymalloc(sizeof(int) * NTask);
      nsend = mymalloc(sizeof(int) * NTask * NTask);
      ndonelist = mymalloc(sizeof(int) * NTask);

      if(All.ComovingIntegrationOn)
	a3inv = 1 / (All.Time * All.Time * All.Time);
      else
	a3inv = 1;

      egysum = 0;

      for(n = 0, NumSphUpdate = 0; n < N_gas; n++)
	{
	  if(P[n].Type == 0)
	    {
	      NumSphUpdate++;

	      egysum += SphP[n].CR_E0[CRpop];

	      SphP[n].CR_DeltaE[CRpop] = 0;
	      SphP[n].CR_DeltaN[CRpop] = 0;

	      kappa = All.CR_DiffusionCoeff;

	      if(All.CR_DiffusionDensScaling != 0.0)
		kappa *= pow(SphP[n].d.Density * a3inv / All.CR_DiffusionDensZero, All.CR_DiffusionDensScaling);

	      if(All.CR_DiffusionEntropyScaling != 0.0)
		kappa *= pow(SphP[n].Entropy / All.CR_DiffusionEntropyZero, All.CR_DiffusionEntropyScaling);

	      if(SphP[n].CR_E0[CRpop] > 0)
		{
		  CR_q_i = SphP[n].CR_q0[CRpop] * pow(SphP[n].d.Density * a3inv, 0.33333);

		  cr_efac_i =
		    CR_Tab_MeanEnergy(CR_q_i, All.CR_Alpha[CRpop] - 0.3333, CRpop)
		    / CR_Tab_MeanEnergy(CR_q_i, All.CR_Alpha[CRpop], CRpop);

		  kappa *= (All.CR_Alpha[CRpop] - 1) / (All.CR_Alpha[CRpop] - 1.33333) * pow(CR_q_i, 0.3333);

		  kappa_egy = kappa * cr_efac_i;
		}
	      else
		kappa_egy = kappa;

	      SphP[n].CR_Kappa[CRpop] = kappa;
	      SphP[n].CR_Kappa_egy[CRpop] = kappa_egy;
	    }
	}

      MPI_Allreduce(&egysum, &egytot_before, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);

      numlist = mymalloc(NTask * sizeof(int) * NTask);
      MPI_Allgather(&NumSphUpdate, 1, MPI_INT, numlist, 1, MPI_INT, MPI_COMM_WORLD);
      for(i = 0, ntot = 0; i < NTask; i++)
	ntot += numlist[i];
      myfree(numlist);

      /* first, sum the weights */

      i = 0;			/* beginn with this index */
      ntotleft = ntot;		/* particles left for all tasks together */

      while(ntotleft > 0)
	{
	  for(j = 0; j < NTask; j++)
	    nsend_local[j] = 0;

	  /* do local particles and prepare export list */

	  for(nexport = 0, ndone = 0; i < NumPart && nexport < All.BunchSizeDensity - NTask; i++)
	    if(P[i].Type == 0)
	      {
		ndone++;

		for(j = 0; j < NTask; j++)
		  Exportflag[j] = 0;

		compute_diff_weights(i, 0);

		for(j = 0; j < NTask; j++)
		  {
		    if(Exportflag[j])
		      {
			DensDataIn[nexport].Pos[0] = P[i].Pos[0];
			DensDataIn[nexport].Pos[1] = P[i].Pos[1];
			DensDataIn[nexport].Pos[2] = P[i].Pos[2];
			DensDataIn[nexport].Hsml = PPP[i].Hsml;
			DensDataIn[nexport].CR_Kappa[CRpop] = SphP[i].CR_Kappa[CRpop];
			DensDataIn[nexport].CR_Kappa_egy[CRpop] = SphP[i].CR_Kappa_egy[CRpop];

			DensDataIn[nexport].Index = i;
			DensDataIn[nexport].Task = j;
			nexport++;
			nsend_local[j]++;
		      }
		  }
	      }

	  qsort(DensDataIn, nexport, sizeof(struct densdata_in), dens_compare_key);

	  for(j = 1, noffset[0] = 0; j < NTask; j++)
	    noffset[j] = noffset[j - 1] + nsend_local[j - 1];

	  MPI_Allgather(nsend_local, NTask, MPI_INT, nsend, NTask, MPI_INT, MPI_COMM_WORLD);

	  /* now do the particles that need to be exported */

	  for(level = 1; level < (1 << PTask); level++)
	    {
	      for(j = 0; j < NTask; j++)
		nbuffer[j] = 0;
	      for(ngrp = level; ngrp < (1 << PTask); ngrp++)
		{
		  maxfill = 0;
		  for(j = 0; j < NTask; j++)
		    {
		      if((j ^ ngrp) < NTask)
			if(maxfill < nbuffer[j] + nsend[(j ^ ngrp) * NTask + j])
			  maxfill = nbuffer[j] + nsend[(j ^ ngrp) * NTask + j];
		    }
		  if(maxfill >= All.BunchSizeDensity)
		    break;

		  sendTask = ThisTask;
		  recvTask = ThisTask ^ ngrp;

		  if(recvTask < NTask)
		    {
		      if(nsend[ThisTask * NTask + recvTask] > 0 || nsend[recvTask * NTask + ThisTask] > 0)
			{
			  /* get the particles */
			  MPI_Sendrecv(&DensDataIn[noffset[recvTask]],
				       nsend_local[recvTask] * sizeof(struct densdata_in), MPI_BYTE,
				       recvTask, TAG_CONDUCT_A,
				       &DensDataGet[nbuffer[ThisTask]],
				       nsend[recvTask * NTask + ThisTask] * sizeof(struct densdata_in),
				       MPI_BYTE, recvTask, TAG_CONDUCT_A, MPI_COMM_WORLD, &status);
			}
		    }

		  for(j = 0; j < NTask; j++)
		    if((j ^ ngrp) < NTask)
		      nbuffer[j] += nsend[(j ^ ngrp) * NTask + j];
		}

	      for(j = 0; j < nbuffer[ThisTask]; j++)
		compute_diff_weights(j, 1);

	      for(j = 0; j < NTask; j++)
		nbuffer[j] = 0;
	      for(ngrp = level; ngrp < (1 << PTask); ngrp++)
		{
		  maxfill = 0;
		  for(j = 0; j < NTask; j++)
		    {
		      if((j ^ ngrp) < NTask)
			if(maxfill < nbuffer[j] + nsend[(j ^ ngrp) * NTask + j])
			  maxfill = nbuffer[j] + nsend[(j ^ ngrp) * NTask + j];
		    }
		  if(maxfill >= All.BunchSizeDensity)
		    break;

		  sendTask = ThisTask;
		  recvTask = ThisTask ^ ngrp;

		  if(recvTask < NTask)
		    {
		      if(nsend[ThisTask * NTask + recvTask] > 0 || nsend[recvTask * NTask + ThisTask] > 0)
			{
			  /* send the results */
			  MPI_Sendrecv(&DensDataResult[nbuffer[ThisTask]],
				       nsend[recvTask * NTask + ThisTask] * sizeof(struct densdata_out),
				       MPI_BYTE, recvTask, TAG_CONDUCT_B,
				       &DensDataPartialResult[noffset[recvTask]],
				       nsend_local[recvTask] * sizeof(struct densdata_out),
				       MPI_BYTE, recvTask, TAG_CONDUCT_B, MPI_COMM_WORLD, &status);

			  /* add the result to the particles */
			  for(j = 0; j < nsend_local[recvTask]; j++)
			    {
			      source = j + noffset[recvTask];
			      place = DensDataIn[source].Index;

			      SphP[place].CR_WeightSum += DensDataPartialResult[source].CR_WeightSum;
			      SphP[place].CR_WeightSum_egy += DensDataPartialResult[source].CR_WeightSum_egy;
			    }
			}
		    }

		  for(j = 0; j < NTask; j++)
		    if((j ^ ngrp) < NTask)
		      nbuffer[j] += nsend[(j ^ ngrp) * NTask + j];
		}
	      level = ngrp - 1;
	    }

	  MPI_Allgather(&ndone, 1, MPI_INT, ndonelist, 1, MPI_INT, MPI_COMM_WORLD);
	  for(j = 0; j < NTask; j++)
	    ntotleft -= ndonelist[j];
	}

  /*************** now do the diffusion step itself */

      numlist = mymalloc(NTask * sizeof(int) * NTask);
      MPI_Allgather(&NumSphUpdate, 1, MPI_INT, numlist, 1, MPI_INT, MPI_COMM_WORLD);
      for(i = 0, ntot = 0; i < NTask; i++)
	ntot += numlist[i];
      myfree(numlist);

      /* first, sum the weights */

      i = 0;			/* beginn with this index */
      ntotleft = ntot;		/* particles left for all tasks together */

      while(ntotleft > 0)
	{
	  for(j = 0; j < NTask; j++)
	    nsend_local[j] = 0;

	  /* do local particles and prepare export list */

	  for(nexport = 0, ndone = 0; i < NumPart && nexport < All.BunchSizeDensity - NTask; i++)
	    if(P[i].Type == 0)
	      {
		ndone++;

		for(j = 0; j < NTask; j++)
		  Exportflag[j] = 0;

		scatter_diffusion(i, 0);

		for(j = 0; j < NTask; j++)
		  {
		    if(Exportflag[j])
		      {
			DensDataIn[nexport].Pos[0] = P[i].Pos[0];
			DensDataIn[nexport].Pos[1] = P[i].Pos[1];
			DensDataIn[nexport].Pos[2] = P[i].Pos[2];

			DensDataIn[nexport].CR_Kappa[CRpop] = SphP[i].CR_Kappa[CRpop];
			DensDataIn[nexport].CR_Kappa_egy[CRpop] = SphP[i].CR_Kappa_egy[CRpop];
			DensDataIn[nexport].CR_WeightSum = SphP[i].CR_WeightSum;
			DensDataIn[nexport].CR_WeightSum_egy = SphP[i].CR_WeightSum_egy;
			DensDataIn[nexport].Hsml = PPP[i].Hsml;
			DensDataIn[nexport].CR_E0[CRpop] = SphP[i].CR_E0[CRpop];
			DensDataIn[nexport].CR_n0[CRpop] = SphP[i].CR_n0[CRpop];

			DensDataIn[nexport].Index = i;
			DensDataIn[nexport].Task = j;
			nexport++;
			nsend_local[j]++;
		      }
		  }
	      }

	  qsort(DensDataIn, nexport, sizeof(struct densdata_in), dens_compare_key);

	  for(j = 1, noffset[0] = 0; j < NTask; j++)
	    noffset[j] = noffset[j - 1] + nsend_local[j - 1];

	  MPI_Allgather(nsend_local, NTask, MPI_INT, nsend, NTask, MPI_INT, MPI_COMM_WORLD);

	  /* now do the particles that need to be exported */

	  for(level = 1; level < (1 << PTask); level++)
	    {
	      for(j = 0; j < NTask; j++)
		nbuffer[j] = 0;
	      for(ngrp = level; ngrp < (1 << PTask); ngrp++)
		{
		  maxfill = 0;
		  for(j = 0; j < NTask; j++)
		    {
		      if((j ^ ngrp) < NTask)
			if(maxfill < nbuffer[j] + nsend[(j ^ ngrp) * NTask + j])
			  maxfill = nbuffer[j] + nsend[(j ^ ngrp) * NTask + j];
		    }
		  if(maxfill >= All.BunchSizeDensity)
		    break;

		  sendTask = ThisTask;
		  recvTask = ThisTask ^ ngrp;

		  if(recvTask < NTask)
		    {
		      if(nsend[ThisTask * NTask + recvTask] > 0 || nsend[recvTask * NTask + ThisTask] > 0)
			{
			  /* get the particles */
			  MPI_Sendrecv(&DensDataIn[noffset[recvTask]],
				       nsend_local[recvTask] * sizeof(struct densdata_in), MPI_BYTE,
				       recvTask, TAG_CONDUCT_A,
				       &DensDataGet[nbuffer[ThisTask]],
				       nsend[recvTask * NTask + ThisTask] * sizeof(struct densdata_in),
				       MPI_BYTE, recvTask, TAG_CONDUCT_A, MPI_COMM_WORLD, &status);
			}
		    }

		  for(j = 0; j < NTask; j++)
		    if((j ^ ngrp) < NTask)
		      nbuffer[j] += nsend[(j ^ ngrp) * NTask + j];
		}

	      for(j = 0; j < nbuffer[ThisTask]; j++)
		scatter_diffusion(j, 1);

	      level = ngrp - 1;
	    }

	  MPI_Allgather(&ndone, 1, MPI_INT, ndonelist, 1, MPI_INT, MPI_COMM_WORLD);
	  for(j = 0; j < NTask; j++)
	    ntotleft -= ndonelist[j];
	}

      /* now set the new cosmic ray prorperties */

      egysum = 0;

      for(n = 0; n < N_gas; n++)
	{
	  if(P[n].Type == 0)
	    {
	      SphP[n].CR_E0[CRpop] = SphP[n].CR_DeltaE[CRpop];
	      SphP[n].CR_n0[CRpop] = SphP[n].CR_DeltaN[CRpop];

	      egysum += SphP[n].CR_E0[CRpop];

	      SphP[n].CR_DeltaE[CRpop] = 0;
	      SphP[n].CR_DeltaN[CRpop] = 0;

	      if(SphP[n].CR_n0[CRpop] > 1.0e-12 && SphP[n].CR_E0[CRpop] > 0)
		{
		  meanKineticEnergy = SphP[n].CR_E0[CRpop] * m_p / SphP[n].CR_n0[CRpop];

		  qmeanKin = CR_q_from_mean_kinetic_energy(meanKineticEnergy[CRpop], CRpop);

		  SphP[n].CR_q0[CRpop] = qmeanKin * pow(SphP[n].d.Density * a3inv, -(1.0 / 3.0));
		  SphP[n].CR_C0[CRpop] = SphP[n].CR_n0[CRpop] * (All.CR_Alpha[CRpop] - 1.0) *
		    pow(SphP[n].CR_q0[CRpop], All.CR_Alpha[CRpop] - 1.0);
		}
	      else
		{
		  SphP[n].CR_E0[CRpop] = 0.0;
		  SphP[n].CR_n0[CRpop] = 0.0;

		  SphP[n].CR_q0[CRpop] = 1.0e10;
		  SphP[n].CR_C0[CRpop] = 0.0;
		}
	    }
	}

      MPI_Allreduce(&egysum, &egytot, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
      if(ThisTask == 0)
	{
	  printf("Energy before/after= %g | %g\n", egytot_before, egytot);
	  fflush(stdout);
	}

      myfree(ndonelist);
      myfree(nsend);
      myfree(nsend_local);
      myfree(nbuffer);
      myfree(noffset);

      All.TimeOfLastDiffusion = All.Time;
    }
}

void compute_diff_weights(int target, int mode)
{
  int j, n;
  int startnode, numngb_inbox;
  double h;
  double weightsum, weightsum_egy, kappa, kappa_egy, kappaeff, kappaeff_egy;;
  double dx, dy, dz, r, r2;
  MyFloat *pos;

  if(mode == 0)
    {
      pos = P[target].Pos;
      h = PPP[target].Hsml;
      kappa = SphP[target].CR_Kappa;
      kappa_egy = SphP[target].CR_Kappa_egy;
    }
  else
    {
      pos = DensDataGet[target].Pos;
      h = DensDataGet[target].Hsml;
      kappa = DensDataGet[target].CR_Kappa;
      kappa_egy = DensDataGet[target].CR_Kappa_egy;
    }

  weightsum = weightsum_egy = 0;

  startnode = All.MaxPart;

  kappaeff = 4 * kappa * (All.Time - All.TimeOfLastDiffusion);
  kappaeff_egy = 4 * kappa_egy * (All.Time - All.TimeOfLastDiffusion);

  if(kappaeff > 0)
    {
      do
	{
	  numngb_inbox = ngb_treefind_variable(&pos[0], h, &startnode);

	  for(n = 0; n < numngb_inbox; n++)
	    {
	      j = Ngblist[n];

	      dx = pos[0] - P[j].Pos[0];
	      dy = pos[1] - P[j].Pos[1];
	      dz = pos[2] - P[j].Pos[2];

#ifdef PERIODIC			/*  now find the closest image in the given box size  */
	      if(dx > boxHalf_X)
		dx -= boxSize_X;
	      if(dx < -boxHalf_X)
		dx += boxSize_X;
	      if(dy > boxHalf_Y)
		dy -= boxSize_Y;
	      if(dy < -boxHalf_Y)
		dy += boxSize_Y;
	      if(dz > boxHalf_Z)
		dz -= boxSize_Z;
	      if(dz < -boxHalf_Z)
		dz += boxSize_Z;
#endif
	      r2 = dx * dx + dy * dy + dz * dz;

	      if(r2 < h * h)
		{
		  r = sqrt(r2);

		  weightsum += P[j].Mass * exp(-r2 / kappaeff);
		  weightsum_egy += P[j].Mass * exp(-r2 / kappaeff_egy);
		}
	    }
	}
      while(startnode >= 0);
    }

  if(mode == 0)
    {
      SphP[target].CR_WeightSum = weightsum;
      SphP[target].CR_WeightSum_egy = weightsum_egy;
    }
  else
    {
      DensDataResult[target].CR_WeightSum = weightsum;
      DensDataResult[target].CR_WeightSum_egy = weightsum_egy;
    }

}

void scatter_diffusion(int target, int mode)
{
  int j, n;
  int startnode, numngb_inbox;
  double h, weight;
  double weightsum, kappa, kappaeff, CR_E0, CR_n0;
  double weightsum_egy, kappa_egy, kappaeff_egy;
  double dx, dy, dz, r, r2;
  MyFloat *pos;

  if(mode == 0)
    {
      pos = P[target].Pos;
      h = PPP[target].Hsml;
      kappa = SphP[target].CR_Kappa;
      kappa_egy = SphP[target].CR_Kappa_egy;
      weightsum = SphP[target].CR_WeightSum;
      weightsum_egy = SphP[target].CR_WeightSum_egy;
      CR_E0 = SphP[target].CR_E0;
      CR_n0 = SphP[target].CR_n0;
    }
  else
    {
      pos = DensDataGet[target].Pos;
      h = DensDataGet[target].Hsml;
      kappa = DensDataGet[target].CR_Kappa;
      kappa_egy = DensDataGet[target].CR_Kappa_egy;
      weightsum = DensDataGet[target].CR_WeightSum;
      weightsum_egy = DensDataGet[target].CR_WeightSum_egy;
      CR_E0 = DensDataGet[target].CR_E0;
      CR_n0 = DensDataGet[target].CR_n0;
    }

  startnode = All.MaxPart;

  kappaeff = 4 * kappa * (All.Time - All.TimeOfLastDiffusion);
  kappaeff_egy = 4 * kappa_egy * (All.Time - All.TimeOfLastDiffusion);

  if(kappaeff > 0)
    {
      do
	{
	  numngb_inbox = ngb_treefind_variable(&pos[0], h, &startnode);

	  for(n = 0; n < numngb_inbox; n++)
	    {
	      j = Ngblist[n];

	      dx = pos[0] - P[j].Pos[0];
	      dy = pos[1] - P[j].Pos[1];
	      dz = pos[2] - P[j].Pos[2];

#ifdef PERIODIC			/*  now find the closest image in the given box size  */
	      if(dx > boxHalf_X)
		dx -= boxSize_X;
	      if(dx < -boxHalf_X)
		dx += boxSize_X;
	      if(dy > boxHalf_Y)
		dy -= boxSize_Y;
	      if(dy < -boxHalf_Y)
		dy += boxSize_Y;
	      if(dz > boxHalf_Z)
		dz -= boxSize_Z;
	      if(dz < -boxHalf_Z)
		dz += boxSize_Z;
#endif
	      r2 = dx * dx + dy * dy + dz * dz;

	      if(r2 < h * h)
		{
		  r = sqrt(r2);

		  weight = P[j].Mass * exp(-r2 / kappaeff) / weightsum;
		  SphP[j].CR_DeltaN += CR_n0 * weight;

		  weight = P[j].Mass * exp(-r2 / kappaeff_egy) / weightsum_egy;
		  SphP[j].CR_DeltaE += CR_E0 * weight;

		}
	    }
	}
      while(startnode >= 0);
    }
}

#endif


/*! \file vhartree.h
    \brief Hartreeポテンシャルを求めるクラスの宣言

    Copyright ©  2015 @dc1394 All Rights Reserved.
    This software is released under the BSD 2-Clause License.
*/

#ifndef _VHARTREE_H_
#define _VHARTREE_H_

#pragma once

#include "diffdata.h"
#include "property.h"
#include <memory>           // for std::unique_ptr
#include <gsl/gsl_spline.h> // for gsl_interp_accel, gsl_interp_accel_free, gsl_spline, gsl_spline_free

namespace schrac {
    //! A class.
    /*!
        Hartreeポテンシャルを求めるクラス
    */
    class Vhartree final {
        // #region コンストラクタ・デストラクタ

    public:
        //! A constructor.
        /*!
            唯一のコンストラクタ
            \param r_mesh_ rのメッシュ
        */
        Vhartree(std::vector<double> const & r_mesh);

        //! A private copy constructor.
        /*!
            コピーコンストラクタ
        */
        Vhartree(Vhartree const & rhs);

        //! A destructor.
        /*!
            デフォルトデストラクタ
        */
        ~Vhartree() = default;

        // #region メンバ関数

        //!  A public member function (const).
        /*!
            Hartreeポテンシャルの微分値を返す
            \param r 極座標のr
            \return Hartreeポテンシャルの微分値
        */
        double dvhartree_dr(double r) const;

        //!  A public member function.
        /*!
            Hartreeポテンシャルが境界条件を満たすようにセットする
            \param Z 原子核の電荷
        */
        void set_vhartree_boundary_condition(double Z);

        //!  A public member function.
        /*!
            Hartreeポテンシャルを初期化する
        */
        void vhart_init();

        //!  A public member function (const).
        /*!
            Hartreeポテンシャルの値を返す
            \param r 極座標のr
            \return Hartreeポテンシャルの値
        */
        double vhartree(double r) const;

        // #endregion メンバ関数

        // #region プロパティ

    public:
        //! A property.
        /*!
            Hartreeポテンシャルが格納された可変長配列へのプロパティ
        */
        Property<std::vector<double>> Vhart;

        // #endregion プロパティ

        // #region メンバ変数

    private:
        //! A private member variable.
        /*!
            gsl_interp_accelへのスマートポインタ
        */
        std::unique_ptr<gsl_interp_accel, decltype(&gsl_interp_accel_free)> const acc_;

        //! A private member variable.
        /*!
            rのメッシュが格納された可変長配列
        */
        std::vector<double> const r_mesh_;

        //! A private member variable.
        /*!
            gsl_interp_typeへのスマートポインタ
        */
        std::unique_ptr<gsl_spline, decltype(&gsl_spline_free)> const spline_;

        //! A private member variable.
        /*!
            Hartreeポテンシャルが格納された可変長配列
        */
        std::vector<double> vhart_;

        // #endregion メンバ変数

        // #region 禁止されたコンストラクタ・メンバ関数

    private:
        //! A private constructor (deleted).
        /*!
            デフォルトコンストラクタ（禁止）
        */
        Vhartree() = delete;

        //! A private member function (deleted).
        /*!
            operator=()の宣言（禁止）
            \param コピー元のオブジェクト（未使用）
            \return コピー元のオブジェクト
        */
        Vhartree & operator=(Vhartree const &) = delete;

        // #endregion 禁止されたコンストラクタ・メンバ関数
    };
}

#endif  // _VHARTREE_H_


#include <stdio.h>
#include <math.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_odeiv.h>
#define DIM 1
#define NPTS 100

#define abserr 1e-5
#define relerr 1e-5
#define mem 10000
typedef double (* function) (double x, void * params);
double integrate(function f, void * params, double lower_bound, double upper_bound);

//int func (double t, const double y[], double f[], void *params);
//void gsl_ode(gsl_odeiv_system sys, double Xo, double maxtime);