/**
 * @file bblas_sutil.c
 *
 * @brief BBLAS testing utilities for float 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
 *
 * Contains routines used in the testing to modify randomly generated matrices
 * and compute the average error over an entire batch etc.
 *
 **/

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

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

/** Include real functions since using float real precision **/
#define REAL

/** Quick access to the matrix elements **/
#define A(i,j)  A[i + j*lda]

/**
 * Make a matrix symmetric/symmetric.  Makes diagonal real.
 * Sets Aji = conj( Aij ) for j < i, that is, copy & conjugate
 * lower triangle to upper triangle.
 **/

void bblas_smake_symmetric(int lda, int N, float* A)
{
    int i, j;
    for( i=0; i < N; ++i ) {
        A(i,i) = creal( A(i,i) );
        for( j=0; j < i; ++j ) {
            A(j,i) = conj( A(i,j) );
        }
    }
}

#ifdef COMPLEX

/**
 * Make a matrix real-symmetric
 * Does NOT make diagonal real.
 * Sets Aji = Aij for j < i, that is,
 * copy lower triangle to upper triangle.
 **/

void bblas_smake_symmetric(int lda, int N, float* A)
{
    int i, j;
    for( i=0; i < N; ++i ) {
        for( j=0; j < i; ++j ) {
            A(j,i) =  A(i,j);
        }
    }
}
#endif

/**
 * irandRange generates a random value (int)
 * in the range min_n and max_n
 **/

int irandRange(int min_n, int max_n)
{
    return rand() % (max_n - min_n + 1) + min_n;
}

/**
 * bblas_srandRange generates a random value (float)
 * in the range [0,max_n]: TODO replace by generic
 */

float bblas_srandRange( int max_n )
{
    return ( float )rand()/( float )( RAND_MAX/max_n );
}

/**
 * Computes statistics of the relative errors to summarise them for the user.
 **/

void bblas_sstatistic(bblas_stest_t *test)
{

    enum BBLAS_ROUTINE routine  = test->routine;

    /*Compute avg(M), avg(N), avg(K) */
    if(test->batch_opts == BBLAS_VARIABLE)
    {
	if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	    (routine == BBLAS_HEMM) || (routine == BBLAS_TRMM) ||
	    (routine == BBLAS_TRSM))
	{
	    test->avgM = bblas_avgarrayI(test->M, test->batch_count);
	}

	if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYRK)  ||
	    (routine == BBLAS_HERK) || (routine == BBLAS_SYR2K) ||
	    (routine == BBLAS_HER2K))
	{
	    test->avgK = bblas_avgarrayI(test->K, test->batch_count);
	}

	test->avgN = bblas_avgarrayI(test->N, test->batch_count);

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

	if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	    (routine == BBLAS_HEMM) || (routine == BBLAS_TRMM) ||
	    (routine == BBLAS_TRSM))
	{
	    test->avgM = test->M[0];
	}

	if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYRK)  ||
	    (routine == BBLAS_HERK) || (routine == BBLAS_SYR2K) ||
	    (routine == BBLAS_HER2K))
	{
	    test->avgK = test->K[0];
	}
	test->avgN = test->N[0];

    } else
    {
	bblas_error("testing_sgemm_batch.c", "wrong batch_opts value");
    }

    /*Statistics on the error */
    switch(test->target)
    {
    case BBLAS_MKL:
      test->mkl_min_error = bblas_sminarrayD(test->mkl_error, test->batch_count);
      test->mkl_avg_error = bblas_savgarrayD(test->mkl_error, test->batch_count);
      test->mkl_max_error = bblas_smaxarrayD(test->mkl_error, test->batch_count);
      test->mkl_std_error = bblas_sstdarrayD(test->mkl_error, test->batch_count);
      break;

    case BBLAS_CUBLAS:
    case BBLAS_MAGMA:
      test->device_min_error = bblas_sminarrayD(test->device_error, test->batch_count);
      test->device_avg_error = bblas_savgarrayD(test->device_error, test->batch_count);
      test->device_max_error = bblas_smaxarrayD(test->device_error, test->batch_count);
      test->device_std_error = bblas_sstdarrayD(test->device_error, test->batch_count);
      break;

    case BBLAS_OTHER:
      test->other_min_error = bblas_sminarrayD(test->other_error, test->batch_count);
      test->other_avg_error = bblas_savgarrayD(test->other_error, test->batch_count);
      test->other_max_error = bblas_smaxarrayD(test->other_error, test->batch_count);
      test->other_std_error = bblas_sstdarrayD(test->other_error, test->batch_count);
      break;

    default:
      printf("In bblas_sstatistic(): Target no defined\n");
      exit(EXIT_FAILURE);
    }
}

/**
 * Print a matrix.
 **/
void bblas_sprintmatrix(float *matrix, int row, int col)
{
    /*Local variables */
    int i, j;

    for (i=0; i < row; i++)
    {
	printf("\n\n");
	for (j=0; j < col; j++)
	{
#ifdef COMPLEX
	    printf("%1.2f + %1.2f\t", creal(matrix[i*col+j]), cimag(matrix[i*col+j]));
#else
	    printf("%1.2f",matrix[i*col+j]);
#endif
	}
    }
    printf("\n");
}

/**
 * Decide whether a batch is fixed or variable.
 **/

char* bblas_getoption(enum BBLAS_OPTS opts)
{
   /*Local variable */
    char funcname[] = "bblas_getoption";

    switch(opts)
    {
	case BBLAS_VARIABLE:
	    return "BATCH OPTION: VARIABLE";
	    break;

	case BBLAS_FIXED:
	    return "BATCH OPTION: FIXED";
	    break;

	default:
	    printf("ERROR in %s, undefined bblas routine name\n",funcname);
	    exit(EXIT_FAILURE);
    }

}

/**
 * Get the name of the current test routine.
 **/

char* bblas_getroutine(enum BBLAS_ROUTINE routine)
{
    /*Local variable */
    char funcname[] = "bblas_getroutine";

    switch(routine)
    {
	case BBLAS_GEMM:
	    return "SGEMM";
	    break;

	case BBLAS_HEMM:
	    return "SSYMM";
	    break;

	case BBLAS_HER2K:
	    return "SSYR2K";
	    break;

	case BBLAS_HERK:
	    return "SSYRK";
	    break;

	case BBLAS_SYMM:
	    return "SSYMM";
	    break;

	case BBLAS_SYR2K:
	    return "SSYR2K";
	    break;

	case BBLAS_SYRK:
	    return "SSYRK";
	    break;

	case BBLAS_TRMM:
	    return "STRMM";
	    break;

	case BBLAS_TRSM:
	    return "STRSM";
	    break;

	default:
	    printf("ERROR in %s, undefined bblas routine name\n",funcname);
	    exit(EXIT_FAILURE);
    }
}

/**
 * Computes the maximum value of an array of real floats.
 **/

float bblas_smaxarrayD(float *myArray, int size)
{
    int iter;
    float maxValue = myArray[0];

    for (iter = 0; iter < size; ++iter)
    {
        if ( myArray[iter] > maxValue )
	{
            maxValue = myArray[iter];
        }
    }
    return maxValue;
}

/**
 * Computes the minimum value of an array of real floats.
 **/

float bblas_sminarrayD(float *myArray, int size)
{
    int iter;
    float minValue = myArray[0];

    for (iter = 0; iter < size; ++iter)
    {
        if ( myArray[iter] < minValue )
	{
            minValue = myArray[iter];
        }
    }
    return minValue;
}

/**
 * Computes the mean value of an array of real floats.
 **/

float bblas_savgarrayD(float *myArray, int size)
{
    int iter;
    float avg = 0.;

    for (iter = 0; iter < size; ++iter)
    {
	avg += myArray[iter];
    }
    return avg/size;
}

/**
 * Computes the standard deviation of an array of real floats.
 **/

float bblas_sstdarrayD(float *myArray, int size)
{
    int iter;
    float avg, sd=0.;

    avg =  bblas_savgarrayD(myArray, size);

    for (iter = 0; iter < size; ++iter)
    {
	sd += (myArray[iter] -avg)*(myArray[iter] -avg);
    }
    return sd/size;
}

/**
 * Computes the minimum value of an array of integers.
 **/

int bblas_minarrayI(int *myArray, int size)
{
    int iter;
    int minValue = myArray[0];

    for (iter = 0; iter < size; ++iter)
    {
        if ( myArray[iter] < minValue )
	{
            minValue = myArray[iter];
        }
    }
    return minValue;
}

/**
 * Computes the mean of an array of integers.
 **/

int bblas_avgarrayI(int *myArray, int size)
{
    int iter;
    int avg = 0;

    for (iter = 0; iter < size; ++iter)
    {
	avg += myArray[iter];
    }
    return avg/size;
}

/**
 * Transform BBLAS enum values for <tt>trans</tt>, <tt>uplo</tt> etc. to human-readable strings.
 **/

char* bblas_op2char(unsigned int op)
{
    char *opname = (char*)malloc(30*sizeof(char));
    switch(op)
    {
        case BblasNoTrans:
            strcpy(opname,"CblasNoTrans");
            break;

        case BblasTrans:
	    strcpy(opname,"CblasTrans");
            break;

        case BblasConjTrans:
            strcpy(opname,"CblasConjTrans");
            break;

        case BblasLower:
            strcpy(opname,"CblasLower");
            break;

        case BblasUpper:
	    strcpy(opname,"CblasUpper");
            break;

        case BblasNonUnit:
	    strcpy(opname,"CblasNonUnit");
            break;

        case BblasUnit:
	   strcpy(opname,"CblasUnit");
            break;

        case BblasLeft:
	   strcpy(opname,"CblasLeft");
            break;

        case BblasRight:
	    strcpy(opname,"CblasRight");
            break;

        default:
            return 0;
            exit(EXIT_FAILURE);
    }
    return opname;
}

/**
 * Get the amount of data needed.
 **/

int bblas_snbdata(bblas_stest_t *test)
{
    enum BBLAS_OPTS batch_opts        = test->batch_opts;
    int nb_data;
    char function_name[NAME_LENGTH]   ="bblas_getdatacount";

    switch(batch_opts)
    {
	case BBLAS_VARIABLE:
	    nb_data =  test->batch_count;
	    break;

	case BBLAS_FIXED:
	    nb_data = 1;
	    break;

	default:
	    bblas_fatal_error(function_name, "wrong batch_opts value");
    }

    return nb_data;
}

/**
 * Inject an error into the computation if this is set in the input file.
 **/

void bblas_sset_error(bblas_stest_t *test)
{
    int   nb_data =  bblas_snbdata(test);
    int   routine = test->routine;
    int   error_index = irandRange(0, nb_data);

    if (test->global_error){
	test->batch_count = -1;
	return;
    }

    if (test->batch_opts == BBLAS_FIXED)
    {
	error_index = 0;
    }

    if (test->set_error)
    {
	if ((routine == BBLAS_GEMM) || (routine == BBLAS_SYMM) ||
	    (routine == BBLAS_HEMM) || (routine == BBLAS_TRMM) ||
	    (routine == BBLAS_TRSM))
	{
	    test->M[error_index] = -1;
	}else if ((routine == BBLAS_SYRK) || (routine == BBLAS_HERK) ||
		  (routine == BBLAS_SYR2K)|| (routine == BBLAS_HER2K))
	{
	    test->K[error_index] = -1;
	}
    }
}

/**
 * Check whether a computation has passed or failed our accuracy test.
 * When new_accuracy=1 in the input file this uses an appropriate
 * forward/backward error bound,
 * otherwise this looks at the relative error.
 **/

void bblas_spassed_failed(bblas_stest_t  *test, float error, char *result, int info)
{
    float eps = LAPACKE_slamch_work('e');

	/* Use our new accuracy test based on forward/backward error analysis*/
    if (test->new_accuracy)
    {
	if( (error > 1) || (info))
	{
	    strcpy(result, "FAILED");

	}else
	{
	    strcpy(result, "PASSED");
	}
    }else
    {
		/* Use old accuracy test based on the relative error */
	if((error > eps*test->tolerance) || (info))
	{
	    strcpy(result, "FAILED");

	}else
	{
	    strcpy(result, "PASSED");
	}
    }
}

/**
 * Set the batch_count.
 **/

void bblas_sset_batch_count(bblas_stest_t *test)
{

  test->batch_count = test->minbatch_count* (test->current_iter+1);
}

#undef REAL


#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <cblas.h>
#include <string.h> // memcpy
#include <math.h>

#include "cgraph.h"
#include "cg_operation.h"
#include "cg_types.h"
#include "cg_variables.h"
#include "cg_errors.h"
#include "cg_constants.h"
#include "cg_enums.h"
#include "cg_factory.h"
#include "cg_math.h"

void freeDoubleValue(CGDouble* v){
}

void freeVectorValue(CGVector* data){
	free(data->data);
}

void freeMatrixValue(CGMatrix* data){
	free(data->data);
}

void freeNode(CGraph* graph, CGNode* node){
	//printf("freeing node %d\n", node->type);
	if(node->result != NULL){
		freeResultNode(node->result);
		free(node->result);
		node->result = NULL;
	}

	if(node->diff != NULL){
		freeNode(graph, node->diff);
		free(node->diff);
	}

	switch(node->type){
		case CGNT_CONSTANT:{
			switch(node->constant->type){
				case CGVT_DOUBLE:
				{
					free(node->constant->value);
					free(node->constant);

					break;
				}

				case CGVT_VECTOR:{
					CGVector* vec = (CGVector*)node->constant->value;
					freeVectorValue(vec);
					free(node->constant->value);
					free(node->constant);

					break;
				}

				case CGVT_MATRIX:{
					CGMatrix* mat = (CGMatrix*)node->constant->value;
					freeMatrixValue(mat);
					free(node->constant->value);
					free(node->constant);
					break;
				}
			}
			break;
		}
		case CGNT_VARIABLE:{
			free(node->var);
			break;
		}
		case CGNT_BINARY_OPERATION:
			free(node->bop);
			break;
		case CGNT_UNARY_OPERATION:
			free(node->uop);
			break;
		case CGNT_AXIS_BOUND_OPERATION:
			free(node->axop);
			break;
		case CGNT_GRAPH:
			free(node->graph);
			break;
		case CGNT_CROSS_ENTROPY_LOSS_FUNC:
			free(node->crossEntropyLoss);
			break;
	}

}

void freeResultNode(CGResultNode* node){
	if(node->error){
		free(node->error);
		free(node);
		return;
	}

	switch(node->type){
		case CGVT_DOUBLE:
			freeDoubleValue(node->value);
			break;
		case CGVT_VECTOR:
			freeVectorValue(node->value);
			break;
		case CGVT_MATRIX:
			freeMatrixValue(node->value);
			break;
	}

	free(node->value);
}

void freeGraph(CGraph* graph){
	if(graph == NULL)
		return;

	const char *key;

	map_iter_t iter = map_iter(&graph->vars);

	while ((key = map_next(&graph->vars, &iter))) {
		CGNode* node = *map_get(&graph->vars, key);
		if(node != NULL){
			freeNode(graph, node);
			free(node);
		}
	}

	map_deinit(&graph->vars);

	int i = 0;
	CGNode* node;

	vec_foreach(&graph->nodes, node, i) {
		freeNode(graph, node);
		free(node);
	}

	vec_deinit(&graph->nodes);
	// graph pointer must be freed elsewhere. in lua API we create a copy so we cannot free the parameter of this function.
}

/*
 * Lua lightuser data are automatically freed within the lua VM,
 * this version is lua friendly.
 */
void freeGraph_lua(CGraph* graph){
	if(graph == NULL)
		return;

	const char *key;

	map_deinit(&graph->vars);

	int i = 0;
	CGNode* node;

	vec_deinit(&graph->nodes);
	// graph pointer must be freed elsewhere. in lua API we create a copy so we cannot free the parameter of this function.
}

void freeNode_lua(CGraph* graph, CGNode* node){
	//printf("freeing node %d\n", node->type);

	if(node->result != NULL){
		freeResultNode(node->result);
		free(node->result);
		node->result = NULL;
	}

	if(node->diff != NULL){
		freeNode(graph, node->diff);
		free(node->diff);
	}

	switch(node->type){
		case CGNT_CONSTANT:{
			switch(node->constant->type){
				case CGVT_DOUBLE:
				{
					free(node->constant->value);
					free(node->constant);

					break;
				}

				case CGVT_VECTOR:{
					CGVector* vec = (CGVector*)node->constant->value;
					freeVectorValue(vec);
					free(node->constant->value);
					free(node->constant);

					break;
				}

				case CGVT_MATRIX:{
					CGMatrix* mat = (CGMatrix*)node->constant->value;
					freeMatrixValue(mat);
					free(node->constant->value);
					free(node->constant);
					break;
				}
			}
			break;
		}
		case CGNT_VARIABLE:{
			free(node->var);
			break;
		}
		case CGNT_BINARY_OPERATION:
			free(node->bop);
			break;
		case CGNT_UNARY_OPERATION:
			free(node->uop);
			break;
		case CGNT_AXIS_BOUND_OPERATION:
			free(node->axop);
			break;
		case CGNT_GRAPH:
			free(node->graph);
			break;
		case CGNT_CROSS_ENTROPY_LOSS_FUNC:
			free(node->crossEntropyLoss);
			break;
	}

}


#include <cblas.h>
#include <lapacke.h>
#include <malloc.h>
#include <math.h>
#include <phobos.h>
#include <stdio.h>
#include <stdlib.h>

#define min(x, y)       (((x) < (y)) ? (x) : (y))
#define max(x, y)       (((x) > (y)) ? (x) : (y))
#define KRONDELTA(i, j) (((i) == (j)) ? (1.0) : (0.0))

void
copyPointer(double *initial, double *destination, int elements)
{
  int i;
  for (i = 0; i < elements; ++i)
    destination[i] = initial[i];
}

double
secondNorm(double *vec, int elements)
{
  int i;
  double mag = .0;
  for (i = 0; i < elements; ++i)
    mag += (vec[i] * vec[i]);
  return sqrt(mag);
}

double
infiniteNorm(double *vec, int elements)
{
  int i;
  double res = 0;
  for (i = 0; i < elements; ++i)
    res = max(res, fabs(vec[i]));
  return res;
}

double *
allocateWork(int worksize)
{
  int i;
  double *work = (double *) malloc((worksize) * sizeof(double));
  for (i = 0; i < (worksize); ++i)
    work[i] = .0;
  return work;
}

double
computeRho(
    void (*func)(double *parVec, double *meaVec, int par, int mea, void *data),
    double *parNew,
    double *delta,
    double *g,
    double *epsilon,
    double *epsilonNew,
    double *meaVec,
    double mu,
    int par,
    int mea,
    void *data)
{
  int i;
  double rho = .0, eMag, eNewMag, denom;
  func(parNew, epsilonNew, par, mea, data);
  for (i = 0; i < mea; ++i)
    epsilonNew[i] = (meaVec[i] - epsilonNew[i]);
  eMag    = secondNorm(epsilon, par);
  eNewMag = secondNorm(epsilonNew, par);
  denom   = .0;
  for (i = 0; i < par; ++i)
    denom += (delta[i] * (mu * delta[i] + g[i]));
  rho = (eMag * eMag - eNewMag * eNewMag) / denom;
  return rho;
}

void
numericalGradient(
    void (*func)(double *parVec, double *meaVec, int par, int mea, void *data),
    double *parVec,
    double *jac,
    double *work,
    int par,
    int mea,
    void *data)
{
  int i, j;
  double h;
  h = 1e-4;
  func(parVec, work, par, mea, data);
  double *tmp_mea = work + mea;
  for (i = 0; i < par; ++i)
  {
    parVec[i] += h;
    func(parVec, tmp_mea, par, mea, data);
    for (j = 0; j < mea; ++j)
      jac[j * mea + i] = ((tmp_mea[j] - work[j]) / h);
    parVec[i] -= h;
  }
  tmp_mea = NULL;
}

extern int
phobos(
    void (*func)(double *parVec, double *meaVec, int par, int mea, void *data),
    void (*jac)(double *parVec, double *jacobi, int par, int mea, void *data),
    double *parVec,
    double *meaVec,
    int par,
    int mea,
    int itmax,
    double *opts,
    double *info,
    double *work,
    double *covar,
    void *data)
{
  int i, j, index, freeWork, freeCovar, freeInfo, measXpar, parSq, meaSq,
      worksize, iter, incx, stop, numGrad;
  int *ipiv = (int *) malloc(par * sizeof(int));

  double alpha, beta, mu, muNew, nu, rho, tau, e1, e2, e3, maxHess,
      oneOverThree, compVal;
  double *jacobi, *hessian, *nMatrix, *currMeasure, *epsilon, *tmpMeasure, *g,
      *delta, *pNew, *numWork;

  numWork = NULL;
  if (jac == NULL)
  {
    numGrad = 1;
    numWork = (double *) malloc(2 * mea * sizeof(double));
  }
  else
    numGrad = 0;

  if (opts)
  {
    tau = opts[0];
    e1  = opts[1];
    e2  = opts[2];
    e3  = opts[3];
  }
  else
  {
    tau = STAN_TAU;
    e1  = STAN_EPSILON;
    e2  = STAN_EPSILON;
    e3  = STAN_EPSILON;
  }

  if (info)
    freeInfo = 0;
  else
  {
    info     = (double *) malloc(PHOBOS_INFOSIZE * sizeof(double));
    freeInfo = 1;
  }
  for (i = 0; i < PHOBOS_INFOSIZE; ++i)
    info[i] = .0;

  freeWork  = 0;
  freeCovar = 0;
  stop      = 0;
  iter      = 0;
  incx      = 1;

  measXpar = mea * par;
  parSq    = par * par;
  meaSq    = mea * mea;
  worksize = PHOBOS_WORKSIZE(par, mea);

  // cblas_dgemm implementation: C = alpha*A*B + beta*C
  alpha        = 1.0;
  beta         = .0;
  rho          = .0;
  nu           = 2.0;
  maxHess      = .0;
  oneOverThree = 1.0 / 3.0;
  compVal      = .0;

  // Handle the workspace
  if (work == NULL && covar == NULL)
  {
    work     = allocateWork((worksize + parSq));
    covar    = work + worksize;
    freeWork = 1;
  }
  else if (work == NULL || covar == NULL)
  {
    if (work == NULL)
    {
      work     = allocateWork(worksize);
      freeWork = 1;
    }
    else
    {
      covar     = allocateWork(parSq);
      freeCovar = 1;
    }
  }
  // Clean memory
  // Care with worksize: covar matrix is included
  // in standard implementation of PHOBOS_WORKSIZE.
  // This means we might run in trouble when just
  // running i < workspace ans user defined
  // workspace and covar seperatly.
  for (i = 0; i < (worksize - parSq); ++i)
    work[i] = .0;
  for (i = 0; i < parSq; ++i)
    covar[i] = .0;
  for (i = 0; i < mea; ++i)
    meaVec[i] = .0;

  // manage memory
  // for more information see phobos.h
  jacobi      = work;
  hessian     = (work + measXpar);
  nMatrix     = hessian + parSq;
  currMeasure = (nMatrix + parSq);
  epsilon     = currMeasure + mea;
  tmpMeasure  = epsilon + mea;
  g           = tmpMeasure + mea;
  delta       = g + par;
  pNew        = delta + par;

  // Calculate initial jacobi matrix
  if (numGrad)
    numericalGradient(func, parVec, jacobi, numWork, par, mea, data);
  else
    jac(parVec, jacobi, par, mea, data);

  // Calculate estimate for hessian matrix
  cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, par, par, mea, alpha,
              jacobi, par, jacobi, par, beta, hessian, par);

  // find maxHess
  for (i = 0; i < meaSq; i += (mea + 1))
    maxHess = max(maxHess, hessian[i]);

  // calculate mu = tau * maxHess
  mu = tau * maxHess;

  // Calculate current measurement vector
  func(parVec, currMeasure, par, mea, data);

  // Shift the arrays to fit the cblas implementation
  copyPointer(meaVec, epsilon, mea);
  copyPointer(currMeasure, tmpMeasure, mea);

  // calculate epsilon
  cblas_daxpy(mea, -alpha, tmpMeasure, incx, epsilon, incx);
  info[1] = secondNorm(epsilon, mea);

  // Calculate g = J(t) * epsilon
  cblas_dgemv(CblasRowMajor, CblasTrans, mea, par, alpha, jacobi, par, epsilon,
              incx, beta, g, incx);

  info[2] = infiniteNorm(g, par);
  if (info[2] <= e1)
  {
    info[7] = 5.0;
    info[8] = e1;
    stop    = 1;
  }
  while (!stop && (iter < itmax))
  {
    ++iter;
    info[0] += 1.0;
    do
    {
      //         printf("%d", ++it );
      for (i = 0; i < par; ++i)
      {
        for (j = 0; j < par; ++j)
        {
          index          = i * par + j;
          nMatrix[index] = hessian[index] + KRONDELTA(i, j) * mu;
        }
        delta[i] = g[i];
      }

      LAPACKE_dgesv(LAPACK_ROW_MAJOR, par, 1, nMatrix, par, ipiv, delta, 1);
      info[5] = secondNorm(delta, par);
      compVal = (e2 * secondNorm(parVec, par));
      if (info[5] <= compVal)
      {
        info[7] = 1.0;
        info[8] = compVal;
        stop    = 1;
      }
      else
      {
        for (j = 0; j < par; ++j)
          pNew[j] = parVec[j] + delta[j];
        rho = computeRho(func, pNew, delta, g, epsilon, tmpMeasure, meaVec, mu,
                         par, mea, data);
        //            printf( "  %f", rho);
        if (rho > .0)
        {
          copyPointer(pNew, parVec, par);

          if (numGrad)
            numericalGradient(func, parVec, jacobi, numWork, par, mea, data);
          else
            jac(parVec, jacobi, par, mea, data);

          cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, par, par, mea,
                      alpha, jacobi, par, jacobi, par, beta, hessian, par);

          // Calculate current measurement vector
          func(parVec, currMeasure, par, mea, data);
          copyPointer(meaVec, epsilon, par);
          copyPointer(currMeasure, tmpMeasure, par);

          // calculate epsilon
          cblas_daxpy(par, -alpha, tmpMeasure, incx, epsilon, incx);

          // Calculate g = J(t) * epsilon
          cblas_dgemv(CblasRowMajor, CblasTrans, mea, par, alpha, jacobi, par,
                      epsilon, incx, beta, g, incx);

          // calculate mu = tau * maxHess
          muNew = (1.0 - pow((2.0 * rho - 1), 3.0));
          mu *= (max(oneOverThree, muNew));
          nu      = 2.0;
          info[3] = secondNorm(epsilon, mea);
          muNew *= info[3];
          info[4] = infiniteNorm(g, par);
          if (((info[4]) <= e1))
          {
            stop    = 1;
            info[7] = 2.0;
            info[8] = e1;
          }
          else if ((info[3] * info[3] < e3))
          {
            stop    = 1;
            info[7] = 3.0;
            info[8] = e3;
          }
        }
        else
        {
          mu *= nu;
          nu *= 2.0;
        }
      }
    } while (!(rho < 0 || stop));
  }
  if (iter == itmax && info[7] == .0)
  {
    info[7] = 4.0;
    info[8] = (double) itmax;
  }
  for (i = 0; i < meaSq; i += (mea + 1))
    info[6] = max(info[6], hessian[i]);
  /*   info[1] = mu;
     info[2] = nu;
     info[3] = infiniteNorm(g, par);
     info[4] = rho;
  */
  // Clean the workspace
  free(ipiv);
  if (freeWork)
    free(work);
  if (freeCovar)
    free(covar);
  if (numGrad)
    free(numWork);
  if (freeInfo)
    free(info);
  return iter;
}


#include <gsl/gsl_interp.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_interp2d.h>
#include <gsl/gsl_spline2d.h>

const gsl_interp_type   *types[7];
const gsl_interp2d_type *types2d[2];

void init(void)
{
  types[0]   = gsl_interp_linear;
  types[1]   = gsl_interp_polynomial;
  types[2]   = gsl_interp_cspline;
  types[3]   = gsl_interp_cspline_periodic;
  types[4]   = gsl_interp_akima;
  types[5]   = gsl_interp_akima_periodic;
  types[6]   = gsl_interp_steffen;
  types2d[0] = gsl_interp2d_bilinear;
  types2d[1] = gsl_interp2d_bicubic;
}

/* 1D */
gsl_interp *mgsl_interp_alloc(int type, size_t size)
{
  if(types[0] == NULL) init();
  return gsl_interp_alloc(types[type], size);
}

unsigned int mgsl_interp_type_min_size(int type)
{
  if(types[0] == NULL) init();
  return gsl_interp_type_min_size(types[type]);
}

gsl_spline *mgsl_spline_alloc(int type, size_t size)
{
  if(types[0] == NULL) init();
  return gsl_spline_alloc(types[type], size);
}

/* 2D */
gsl_interp2d *mgsl_interp2d_alloc(int type, size_t xsize, size_t ysize)
{
  if(types[0] == NULL) init();
  return gsl_interp2d_alloc(types2d[type], xsize, ysize);
}

unsigned int mgsl_interp2d_type_min_size(int type)
{
  if(types[0] == NULL) init();
  return gsl_interp2d_type_min_size(types2d[type]);
}

gsl_spline2d *mgsl_spline2d_alloc(int type, size_t xsize, size_t ysize)
{
  if(types[0] == NULL) init();
  return gsl_spline2d_alloc(types2d[type], xsize, ysize);
}


/***********************************************************
 *	Program Name: conv.
 *
 *	A program used to process the data from mcml -
 *	A Monte Carlo simulation of photon distribution in
 *	multi-layered turbid media in ANSI Standard C.
 ****
 *	Creation Date:	11/1991.
 *	Current Date:	6/1992.
 *
 *	Lihong Wang, Ph. D.
 *	Steven L. Jacques, Ph. D.
 *	Laser Biology Research Laboratory - 17
 *	M.D. Anderson Cancer Center
 *	University of Texas
 *	1515 Holcombe Blvd.
 *	Houston, TX 77030
 *	USA
 *
 ****
 *	General Naming Conventions:
 *	Preprocessor names: all capital letters,
 *		e.g. #define PREPROCESSORS
 *	Globals: first letter of each word is capital, no
 *		underscores,
 *		e.g. short GlobalVar;
 *	Dummy variables:  first letter of each word is capital,
 *		and words are connected by underscores,
 *		e.g. void NiceFunction(char Dummy_Var);
 *	Local variables:  all lower cases, words are connected
 *		by underscores,
 *		e.g. short local_var;
 *	Function names or data types:  same as Globals.
 *
 ****
 *	Dimension of length: cm.
 *
 ****/

#ifndef __MCML_CONV_H__
#define __MCML_CONV_H__

#include <math.h>
#include <new>
#include <string>
#include <algorithm>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_sf_bessel.h>
#include "mcml_model.h"

#define GAUSSLIMIT 4
#define PI 3.1415926

#define SIGN(x) ((x)>=0 ? 1:-1)

/****************** Classes *****************************/

class Beam {
/* Beam class - incident light beam class
        Parameters to describe a photon beam.
        Pencil: infinitely narrow beam. This is default for the
            beam from the mcml output.
        Flat:	Flat beam with radius R.
        Gaussian:	Gaussian with 1/e2 radius R.
        Others: general beam described by points with interpolation.
        Class instance variables:
            type - incident beam type, FLAT or GAUSSIAN
            P - total beam power/energy [W or J]
            R - beam radius, defined as 1/e^2 for Gaussian beam [cm]
        Methods:

*/
  public:
    enum BeamType {
        FLAT,
        GAUSSIAN
    };
    BeamType type;			// beam type
    double P;       // total power. [J or W]
    double R;       // radius. [cm]

};

class Node {
/* Node class - node link list binary tree class
        Data structures for the binary tree used to store part of
        the integrand evaluation.

        Class instance variables:
            x - x grid node position
            y - y grid node position
            left - left node pointer
            right - right node pointer
        Methods:
*/

  public:
    double x;
    double y;
    Node * left;
    Node * right;

    Node () : left (nullptr), right (nullptr) {};
    static Node * FillNode(double x, double y);
    static Node * SearchNode(Node * tree, double x);
    static void InsertNode(Node * tree, double x, double y);
    static void FreeTree(Node * tree);
};

class ConvVar {
/* ConvVar class - convoluation variables class
        A global structure to pass the current coordinate of the
        physical quantities being evaluated and the pointers of the
        input and output parameters to the integration function.

        Class instance variables:
            r - r position
            iz - iz index
            ia - ia index
            tree - A tree to store ITheta() & ExpBessI0().
        Methods:

*/

  public:
    double r;
    short iz;
    short ia;
    Node * tree;      // A tree to store ITheta() & ExpBessI0().

    ConvVar () : tree (nullptr) {};
};

class ConvInput {
/* ConvInput class - beam convolution input class
        Input parameters for each independent run.
        z and r are for the cylindrical coordinate system. [cm]
        a is for the angle alpha between the photon exiting
        direction and the surface normal. [radian]
        The grid line separations in z, r, and alpha
        directions are dz, dr, and da respectively.  The numbers
        of grid lines in z, r, and alpha directions are
        nz, nr, and na respectively.
        The member layerspecs will point to an array of
        structures which store parameters of each layer.
        This array has (number_layers + 2) elements. One
        element is for a layer.
        The layers 0 and (num_layers + 1) are for top ambient
        medium and the bottom ambient medium respectively.
        For convolution, the grid line separations in z, and alpha
        directions are still dz, and da respectively.  The numbers
        of grid lines in z, and alpha directions are still
        nz, and na respectively. However, the grid line separation
        and the number of grid lines in r direction are drc and
        nrc respectively.
        Class instance variables:
            beam - incident beam class instance object
            drc - convolution r grid separation.[cm]
            nrc - convolution array range 0..nrc-1.
            eps - relative error in convolution
        Methods:

*/

  public:
    Beam beam;    // incident beam of finite size
    double drc;
    short nrc;
    MCMLModel mcmlModel;
    ConvVar convVar;

    void SelectConvInput (MCMLModel mcmlModelSet,
          Beam::BeamType beamType = Beam::FLAT, double P = 1.0, double R = 0);
    void FreeConvInput();
};

class MCMLConv : public ConvInput {
/* MCMLConv class - multi-layered photon scattering model beam convolution
        inherits from ConvInput beam setup
        Structures for scored physical quantities
        from mcml and to be convolved for photon
        beams of finite size.  Therefore, "Out"
        here means the output of both mcml and conv.
        The member allocated is used to keep the status
        of the arrays.  It is set to 1 if all the arrays
        are allocated and assigned values.  It is set to
        0 otherwise.
        z and r represent z and r coordinates of the
        cylindrical coordinate system. [cm]
        a is the angle alpha between the photon exiting
        direction and the normal to the surfaces. [radian]
        See comments of the InputStruct.
        See manual for the physcial quantities.
        Class instance variables:
            Rd_rac - convolved data. [J/(cm2 sr)]
            Rd_rc - 1D radial distribution of diffuse reflectance [J/cm2]
            A_rzc - 2D probability density in turbid media over r & z [J/cm3]
            Tt_rac - 2D distribution of total transmittance [J/(cm2 sr)]
            Tt_rc - 1D radial distribution of transmittance [J/cm2]
        Methods:

*/
  private:
    void ConvRd_ra ();
    void ConvRd_r ();
    void ConvA_rz ();
    void ConvTt_ra ();
    void ConvTt_r ();
    void ConvA2F();

  public:
    double ** Rd_rac;
    double * Rd_rc;
    double ** A_rzc;
    double ** Tt_rac;
    double * Tt_rc;
    double ** F_rzc;

    MCMLConv () : Rd_rac (nullptr), Rd_rc (nullptr), A_rzc (nullptr),
      Tt_rac (nullptr), Tt_rc (nullptr), F_rzc (nullptr) {};
    void SelectMCMLConv (MCMLModel mcmlModelSet, std::string beamType,
          double P, double R);
    void FreeMCMLConv ();
    void RunConv ();
    double CenterHalfMaxDepth ();
    double SurfaceHalfMaxWidth ();
};

#endif    //__MCML_CONV_H__


/**
 *
 * @file testing_zposv.c
 *
 *  PLASMA testing routines
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Bilel Hadri, Hatem Ltaief
 * @date 2010-11-15
 * @precisions normal z -> c d s
 *
 **/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <plasma.h>
#include <cblas.h>
#include <lapacke.h>
#include <core_blas.h>
#include "testing_zmain.h"

enum blas_order_type {
            blas_rowmajor = 101,
            blas_colmajor = 102 };

enum blas_cmach_type {
            blas_base      = 151,
            blas_t         = 152,
            blas_rnd       = 153,
            blas_ieee      = 154,
            blas_emin      = 155,
            blas_emax      = 156,
            blas_eps       = 157,
            blas_prec      = 158,
            blas_underflow = 159,
            blas_overflow  = 160,
            blas_sfmin     = 161};

enum blas_norm_type {
            blas_one_norm       = 171,
            blas_real_one_norm  = 172,
            blas_two_norm       = 173,
            blas_frobenius_norm = 174,
            blas_inf_norm       = 175,
            blas_real_inf_norm  = 176,
            blas_max_norm       = 177,
            blas_real_max_norm  = 178 };

static void
BLAS_error(char *rname, int err, int val, int x) {
  fprintf( stderr, "%s %d %d %d\n", rname, err, val, x );
  abort();
}

static
void
BLAS_zge_norm(enum blas_order_type order, enum blas_norm_type norm,
  int m, int n, const PLASMA_Complex64_t *a, int lda, double *res) {
  int i, j; float anorm, v;
  char rname[] = "BLAS_zge_norm";

  if (order != blas_colmajor) BLAS_error( rname, -1, order, 0 );

  if (norm == blas_frobenius_norm) {
    anorm = 0.0f;
    for (j = n; j; --j) {
      for (i = m; i; --i) {
        v = a[0];
        anorm += v * v;
        a++;
      }
      a += lda - m;
    }
    anorm = sqrt( anorm );
  } else if (norm == blas_inf_norm) {
    anorm = 0.0f;
    for (i = 0; i < m; ++i) {
      v = 0.0f;
      for (j = 0; j < n; ++j) {
        v += cabs( a[i + j * lda] );
      }
      if (v > anorm)
        anorm = v;
    }
  } else {
    BLAS_error( rname, -2, norm, 0 );
    return;
  }

  if (res) *res = anorm;
}

static
double
BLAS_dpow_di(double x, int n) {
  double rv = 1.0;

  if (n < 0) {
    n = -n;
    x = 1.0 / x;
  }

  for (; n; n >>= 1, x *= x) {
    if (n & 1)
      rv *= x;
  }

  return rv;
}

static
double
BLAS_dfpinfo(enum blas_cmach_type cmach) {
  double eps = 1.0, r = 1.0, o = 1.0, b = 2.0;
  int t = 53, l = 1024, m = -1021;
  char rname[] = "BLAS_dfpinfo";

  if ((sizeof eps) == sizeof(float)) {
    t = 24;
    l = 128;
    m = -125;
  } else {
    t = 53;
    l = 1024;
    m = -1021;
  }

  /* for (i = 0; i < t; ++i) eps *= half; */
  eps = BLAS_dpow_di( b, -t );
  /* for (i = 0; i >= m; --i) r *= half; */
  r = BLAS_dpow_di( b, m-1 );

  o -= eps;
  /* for (i = 0; i < l; ++i) o *= b; */
  o = (o * BLAS_dpow_di( b, l-1 )) * b;

  switch (cmach) {
    case blas_eps: return eps;
    case blas_sfmin: return r;
    default:
      BLAS_error( rname, -1, cmach, 0 );
      break;
  }
  return 0.0;
}

static int check_factorization(int, PLASMA_Complex64_t*, PLASMA_Complex64_t*, int, int , double);
static int check_solution(int, int, PLASMA_Complex64_t*, int, PLASMA_Complex64_t*, PLASMA_Complex64_t*, int, double);
static int check_estimator(PLASMA_enum, int, PLASMA_Complex64_t *, int,
                           PLASMA_Complex64_t *, double, double,
                           double);

int testing_zposv(int argc, char **argv)
{

    /* Check for number of arguments*/
    if (argc != 4){
        USAGE("POSV", "N LDA NRHS LDB",
              "   - N    : the size of the matrix\n"
              "   - LDA  : leading dimension of the matrix A\n"
              "   - NRHS : number of RHS\n"
              "   - LDB  : leading dimension of the RHS B\n");
        return -1;
    }

    int N     = atoi(argv[0]);
    int LDA   = atoi(argv[1]);
    int NRHS  = atoi(argv[2]);
    int LDB   = atoi(argv[3]);
    double eps;
    int info_solution, info_factorization;
    int u, trans1, trans2;

    PLASMA_Complex64_t *A1   = (PLASMA_Complex64_t *)malloc(LDA*N*sizeof(PLASMA_Complex64_t));
    PLASMA_Complex64_t *A2   = (PLASMA_Complex64_t *)malloc(LDA*N*sizeof(PLASMA_Complex64_t));
    PLASMA_Complex64_t *B1   = (PLASMA_Complex64_t *)malloc(LDB*NRHS*sizeof(PLASMA_Complex64_t));
    PLASMA_Complex64_t *B2   = (PLASMA_Complex64_t *)malloc(LDB*NRHS*sizeof(PLASMA_Complex64_t));

    /* Check if unable to allocate memory */
    if ((!A1)||(!A2)||(!B1)||(!B2)){
        printf("Out of Memory \n ");
        return -2;
    }

    eps = BLAS_dfpinfo( blas_eps );

    for(u=0; u<2; u++) {

        trans1 = uplo[u] == PlasmaUpper ? PlasmaConjTrans : PlasmaNoTrans;
        trans2 = uplo[u] == PlasmaUpper ? PlasmaNoTrans : PlasmaConjTrans;

        /*-------------------------------------------------------------
         *  TESTING ZPOSV
         */

        /* Initialize A1 and A2 for Symmetric Positif Matrix */
        PLASMA_zplghe( (double)N, N, A1, LDA, 51 );
        PLASMA_zlacpy( PlasmaUpperLower, N, N, A1, LDA, A2, LDA );

        /* Initialize B1 and B2 */
        PLASMA_zplrnt( N, NRHS, B1, LDB, 371 );
        PLASMA_zlacpy( PlasmaUpperLower, N, NRHS, B1, LDB, B2, LDB );

        printf("\n");
        printf("------ TESTS FOR PLASMA ZPOSV ROUTINE -------  \n");
        printf("            Size of the Matrix %d by %d\n", N, N);
        printf("\n");
        printf(" The matrix A is randomly generated for each test.\n");
        printf("============\n");
        printf(" The relative machine precision (eps) is to be %e \n", eps);
        printf(" Computational tests pass if scaled residuals are less than 60.\n");

        /* PLASMA ZPOSV */
        PLASMA_zposv(uplo[u], N, NRHS, A2, LDA, B2, LDB);

        /* Check the factorization and the solution */
        info_factorization = check_factorization( N, A1, A2, LDA, uplo[u], eps);
        info_solution = check_solution(N, NRHS, A1, LDA, B1, B2, LDB, eps);

        if ( (info_solution == 0) && (info_factorization == 0) ) {
            printf("***************************************************\n");
            printf(" ---- TESTING ZPOSV(%s) ...................... PASSED !\n", uplostr[u]);
            printf("***************************************************\n");
        }
        else {
            printf("***************************************************\n");
            printf(" - TESTING ZPOSV(%s) ... FAILED !\n", uplostr[u]);
            printf("***************************************************\n");
        }

        /*-------------------------------------------------------------
         *  TESTING ZPOTRF + ZPOTRS
         */

        /* Initialize A1 and A2 for Symmetric Positif Matrix */
        PLASMA_zplghe( (double)N, N, A1, LDA, 51 );
        PLASMA_zlacpy( PlasmaUpperLower, N, N, A1, LDA, A2, LDA );

        /* Initialize B1 and B2 */
        PLASMA_zplrnt( N, NRHS, B1, LDB, 371 );
        PLASMA_zlacpy( PlasmaUpperLower, N, NRHS, B1, LDB, B2, LDB );

        /* Plasma routines */
        PLASMA_zpotrf(uplo[u], N, A2, LDA);
        PLASMA_zpotrs(uplo[u], N, NRHS, A2, LDA, B2, LDB);

        printf("\n");
        printf("------ TESTS FOR PLASMA ZPOTRF + ZPOTRS ROUTINE -------  \n");
        printf("            Size of the Matrix %d by %d\n", N, N);
        printf("\n");
        printf(" The matrix A is randomly generated for each test.\n");
        printf("============\n");
        printf(" The relative machine precision (eps) is to be %e \n", eps);
        printf(" Computational tests pass if scaled residuals are less than 60.\n");

        /* Check the factorization and the solution */
        info_factorization = check_factorization( N, A1, A2, LDA, uplo[u], eps);
        info_solution = check_solution(N, NRHS, A1, LDA, B1, B2, LDB, eps);

        if ((info_solution == 0)&(info_factorization == 0)){
            printf("***************************************************\n");
            printf(" ---- TESTING ZPOTRF + ZPOTRS (%s)............ PASSED !\n", uplostr[u]);
            printf("***************************************************\n");
        }
        else{
            printf("****************************************************\n");
            printf(" - TESTING ZPOTRF + ZPOTRS (%s)... FAILED !\n", uplostr[u]);
            printf("****************************************************\n");
        }

        /*-------------------------------------------------------------
         *  TESTING ZPOTRF + ZPTRSM + ZTRSM
         */

        /* Initialize A1 and A2 for Symmetric Positif Matrix */
        PLASMA_zplghe( (double)N, N, A1, LDA, 51 );
        PLASMA_zlacpy( PlasmaUpperLower, N, N, A1, LDA, A2, LDA );

        /* Initialize B1 and B2 */
        PLASMA_zplrnt( N, NRHS, B1, LDB, 371 );
        PLASMA_zlacpy( PlasmaUpperLower, N, NRHS, B1, LDB, B2, LDB );

        /* PLASMA routines */
        PLASMA_zpotrf(uplo[u], N, A2, LDA);
        PLASMA_ztrsm(PlasmaLeft, uplo[u], trans1, PlasmaNonUnit,
                     N, NRHS, 1.0, A2, LDA, B2, LDB);
        PLASMA_ztrsm(PlasmaLeft, uplo[u], trans2, PlasmaNonUnit,
                     N, NRHS, 1.0, A2, LDA, B2, LDB);

        printf("\n");
        printf("------ TESTS FOR PLASMA ZPOTRF + ZTRSM + ZTRSM  ROUTINE -------  \n");
        printf("            Size of the Matrix %d by %d\n", N, N);
        printf("\n");
        printf(" The matrix A is randomly generated for each test.\n");
        printf("============\n");
        printf(" The relative machine precision (eps) is to be %e \n", eps);
        printf(" Computational tests pass if scaled residuals are less than 60.\n");

        /* Check the factorization and the solution */
        info_factorization = check_factorization( N, A1, A2, LDA, uplo[u], eps);
        info_solution = check_solution(N, NRHS, A1, LDA, B1, B2, LDB, eps);

        if ((info_solution == 0)&(info_factorization == 0)){
            printf("***************************************************\n");
            printf(" ---- TESTING ZPOTRF + ZTRSM + ZTRSM (%s)..... PASSED !\n", uplostr[u]);
            printf("***************************************************\n");
        }
        else{
            printf("***************************************************\n");
            printf(" - TESTING ZPOTRF + ZTRSM + ZTRSM (%s)... FAILED !\n", uplostr[u]);
            printf("***************************************************\n");
        }

        /*-------------------------------------------------------------
         *  TESTING ZPOCON on the last call
         */
        {
            double Anorm = PLASMA_zlanhe( PlasmaOneNorm, uplo[u], N, A1, LDA );
            double Acond;

            info_solution = PLASMA_zpocon(uplo[u], N, A2, LDA, Anorm, &Acond);
            if ( info_solution == 0 ) {
                info_solution = check_estimator(uplo[u], N, A1, LDA, A2, Anorm, Acond, eps);
            } else {
                printf(" PLASMA_zpocon returned info = %d\n", info_solution );
            }
            if ((info_solution == 0)){
                printf("***************************************************\n");
                printf(" ---- TESTING ZPOTRF + ZPOCON (%s) ........... PASSED !\n", uplostr[u]);
                printf("***************************************************\n");
            }
            else{
                printf("**************************************************\n");
                printf(" - TESTING ZPOTRF + ZPOCON (%s) ... FAILED !\n", uplostr[u]);
                printf("**************************************************\n");
            }
        }
    }

    free(A1); free(A2); free(B1); free(B2);

    return 0;
}

/*------------------------------------------------------------------------
 *  Check the factorization of the matrix A2
 */
static int check_factorization(int N, PLASMA_Complex64_t *A1, PLASMA_Complex64_t *A2, int LDA, int uplo, double eps)
{
    double Anorm, Rnorm;
    PLASMA_Complex64_t alpha;
    int info_factorization;
    int i,j;

    PLASMA_Complex64_t *Residual = (PLASMA_Complex64_t *)malloc(N*N*sizeof(PLASMA_Complex64_t));
    PLASMA_Complex64_t *L1       = (PLASMA_Complex64_t *)malloc(N*N*sizeof(PLASMA_Complex64_t));
    PLASMA_Complex64_t *L2       = (PLASMA_Complex64_t *)malloc(N*N*sizeof(PLASMA_Complex64_t));
    double *work              = (double *)malloc(N*sizeof(double));

    memset((void*)L1, 0, N*N*sizeof(PLASMA_Complex64_t));
    memset((void*)L2, 0, N*N*sizeof(PLASMA_Complex64_t));

    alpha= 1.0;

    LAPACKE_zlacpy_work(LAPACK_COL_MAJOR,' ', N, N, A1, LDA, Residual, N);

    /* Dealing with L'L or U'U  */
    if (uplo == PlasmaUpper){
        LAPACKE_zlacpy_work(LAPACK_COL_MAJOR,'u', N, N, A2, LDA, L1, N);
        LAPACKE_zlacpy_work(LAPACK_COL_MAJOR,'u', N, N, A2, LDA, L2, N);
        cblas_ztrmm(CblasColMajor, CblasLeft, CblasUpper, CblasConjTrans, CblasNonUnit, N, N, CBLAS_SADDR(alpha), L1, N, L2, N);
    }
    else{
        LAPACKE_zlacpy_work(LAPACK_COL_MAJOR,'l', N, N, A2, LDA, L1, N);
        LAPACKE_zlacpy_work(LAPACK_COL_MAJOR,'l', N, N, A2, LDA, L2, N);
        cblas_ztrmm(CblasColMajor, CblasRight, CblasLower, CblasConjTrans, CblasNonUnit, N, N, CBLAS_SADDR(alpha), L1, N, L2, N);
    }

    /* Compute the Residual || A -L'L|| */
    for (i = 0; i < N; i++)
        for (j = 0; j < N; j++)
           Residual[j*N+i] = L2[j*N+i] - Residual[j*N+i];

    BLAS_zge_norm( blas_colmajor, blas_inf_norm, N, N, Residual, N, &Rnorm );
    BLAS_zge_norm( blas_colmajor, blas_inf_norm, N, N, A1, LDA, &Anorm );

    printf("============\n");
    printf("Checking the Cholesky Factorization \n");
    printf("-- ||L'L-A||_oo/(||A||_oo.N.eps) = %e \n",Rnorm/(Anorm*N*eps));

    if ( isnan(Rnorm/(Anorm*N*eps)) || isinf(Rnorm/(Anorm*N*eps)) || (Rnorm/(Anorm*N*eps) > 60.0) ){
        printf("-- Factorization is suspicious ! \n");
        info_factorization = 1;
    }
    else{
        printf("-- Factorization is CORRECT ! \n");
        info_factorization = 0;
    }

    free(Residual); free(L1); free(L2); free(work);

    return info_factorization;
}

/*------------------------------------------------------------------------
 *  Check the accuracy of the solution of the linear system
 */

static int check_solution(int N, int NRHS, PLASMA_Complex64_t *A1, int LDA, PLASMA_Complex64_t *B1, PLASMA_Complex64_t *B2, int LDB, double eps )
{
    int info_solution;
    double Rnorm, Anorm, Xnorm, Bnorm, result;
    PLASMA_Complex64_t alpha, beta;
    double *work = (double *)malloc(N*sizeof(double));

    alpha = 1.0;
    beta  = -1.0;

    BLAS_zge_norm( blas_colmajor, blas_inf_norm, N, NRHS, B2, LDB, &Xnorm );
    BLAS_zge_norm( blas_colmajor, blas_inf_norm, N, N,    A1, LDA, &Anorm );
    BLAS_zge_norm( blas_colmajor, blas_inf_norm, N, NRHS, B1, LDB, &Bnorm );

    cblas_zgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, N, NRHS, N, CBLAS_SADDR(alpha), A1, LDA, B2, LDB, CBLAS_SADDR(beta), B1, LDB);
    BLAS_zge_norm( blas_colmajor, blas_inf_norm, N, NRHS, B1, LDB, &Rnorm );

    if (getenv("PLASMA_TESTING_VERBOSE"))
      printf( "||A||_oo=%f\n||X||_oo=%f\n||B||_oo=%f\n||A X - B||_oo=%e\n", Anorm, Xnorm, Bnorm, Rnorm );

    result = Rnorm / ( (Anorm*Xnorm+Bnorm)*N*eps ) ;
    printf("============\n");
    printf("Checking the Residual of the solution \n");
    printf("-- ||Ax-B||_oo/((||A||_oo||x||_oo+||B||_oo).N.eps) = %e \n", result);

    if (  isnan(Xnorm) || isinf(Xnorm) || isnan(result) || isinf(result) || (result > 60.0) ) {
        printf("-- The solution is suspicious ! \n");
        info_solution = 1;
     }
    else{
        printf("-- The solution is CORRECT ! \n");
        info_solution = 0;
    }

    free(work);

    return info_solution;
}

/*------------------------------------------------------------------------
 *  Check the accuracy of the condition estimator
 */
static int check_estimator(PLASMA_enum uplo, int N, PLASMA_Complex64_t *A1, int LDA,
                           PLASMA_Complex64_t *A2, double Anorm, double Acond,
                           double eps)
{
    int info_solution;
    double result, Acond_lapack;
    double invcond, invcond_lapack;

    info_solution = LAPACKE_zpocon(LAPACK_COL_MAJOR, lapack_const(uplo), N, A2, LDA, Anorm, &Acond_lapack);

    if ( info_solution != 0 ) {
        printf(" PLASMA_zgecon returned info = %d\n", info_solution );
        return info_solution;
    }

    invcond_lapack = 1. / ( Acond_lapack );
    invcond        = 1. / ( Acond );

    printf("============\n");
    printf("Checking the condition number \n");
    printf("-- Acond_plasma = %e, Acond_lapack = %e \n"
           "-- Ainvcond_plasma = %e, Ainvcond_lapack = %e \n",
           Acond, Acond_lapack, invcond, invcond_lapack );

    result = fabs( Acond_lapack - Acond ) / eps;
    if ( result > 60. ) {
        printf("-- The solution is suspicious ! \n");
        info_solution = 1;
     }
    else{
        printf("-- The solution is CORRECT ! \n");
        info_solution = 0;
    }
    return info_solution;
}


#ifndef __GSLPRINT_H__
#define __GSLPRINT_H__

#include <stdio.h>
#include <iostream>
#include <math.h>
#include <fstream>
#include <gsl/gsl_math.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_poly.h>
#include <gsl/gsl_integration.h>

#endif

void gsl_matrix_fprint(FILE * stream, gsl_matrix * target,int rows,int columns, char const * format);
void gsl_vector_fprint(FILE * stream, gsl_vector * target,int length, char const * format);
void gsl_matrix_printf(gsl_matrix * m, int rows, int columns, char const * format);
void gsl_vector_printf(gsl_vector * m,int length, char const * format);
void gsl_vector_complex_fprint(FILE * stream, gsl_vector_complex * target, int length, char const * format);
void gsl_matrix_complex_fprint(FILE * stream, gsl_matrix_complex * target, int rows, int columns, char const * format);
void gsl_matrix_complex_printf(gsl_matrix_complex * target, int rows, int columns, char const * format);
void gsl_vector_complex_fprint(gsl_vector_complex * target, int length, char const * format);


/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */

#pragma once
#ifdef PADDLE_USE_MKLML
#include <mkl_cblas.h>
#include <mkl_lapacke.h>
#include <mkl_vml_functions.h>
#endif

#ifdef PADDLE_USE_MKL
#include <mkl.h>
#include <mkl_lapacke.h>
#endif

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

#ifdef PADDLE_USE_OPENBLAS
#include <cblas.h>
#include <lapacke.h>
#endif

#ifndef LAPACK_FOUND
extern "C" {
#include <cblas.h>
int LAPACKE_sgetrf(int matrix_layout, int m, int n, float* a, int lda,
                   int* ipiv);
int LAPACKE_dgetrf(int matrix_layout, int m, int n, double* a, int lda,
                   int* ipiv);
int LAPACKE_sgetri(int matrix_layout, int n, float* a, int lda,
                   const int* ipiv);
int LAPACKE_dgetri(int matrix_layout, int n, double* a, int lda,
                   const int* ipiv);
}
#endif

#include <cmath>

#include "paddle/framework/tensor.h"
#include "paddle/platform/device_context.h"
#include "paddle/platform/enforce.h"

namespace paddle {
namespace operators {
namespace math {

// Support continuous memory now
// If transA = N, and transB = N
// Then matrixA: M * K, matrixB: K * N matrixC : M * N
// For more detailed info, please refer to
// http://www.netlib.org/lapack/explore-html/d4/de2/sgemm_8f.html
template <typename Place, typename T>
void gemm(const platform::DeviceContext& context, const CBLAS_TRANSPOSE transA,
          const CBLAS_TRANSPOSE transB, const int M, const int N, const int K,
          const T alpha, const T* A, const T* B, const T beta, T* C);

// matrix multiply with continuous memory
template <typename Place, typename T>
void matmul(const platform::DeviceContext& context,
            const framework::Tensor& matrix_a, bool trans_a,
            const framework::Tensor& matrix_b, bool trans_b, T alpha,
            framework::Tensor* matrix_out, T beta);

}  // namespace math
}  // namespace operators
}  // namespace paddle


/**
 *
 * @file core_zsyssq.c
 *
 *  PLASMA core_blas kernel
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.8.0
 * @author Mathieu Faverge
 * @date 2010-11-15
 * @precisions normal z -> c d s
 *
 **/
#include <math.h>
#include <lapacke.h>
#include "common.h"

#define COMPLEX

#define UPDATE( __nb, __value )                                         \
    if (__value != 0. ){                                                \
        if ( *scale < __value ) {                                       \
            *sumsq = __nb + (*sumsq) * ( *scale / __value ) * ( *scale / __value ); \
            *scale = __value;                                           \
        } else {                                                        \
            *sumsq = *sumsq + __nb * ( __value / *scale ) *  ( __value / *scale ); \
        }                                                               \
    }

/*****************************************************************************
 *
 * @ingroup dplasma_cores_complex64
 *
 *  CORE_zsyssq returns the values scl and ssq such that
 *
 *    ( scl**2 )*ssq = sum( A( i, j )**2 ) + ( scale**2 )*sumsq,
 *                     i,j
 *
 * with i from 0 to N-1 and j form 0 to N-1. The value of sumsq is
 * assumed to be at least unity and the value of ssq will then satisfy
 *
 *    1.0 .le. ssq .le. ( sumsq + 2*n*n ).
 *
 * scale is assumed to be non-negative and scl returns the value
 *
 *    scl = max( scale, abs( real( A( i, j ) ) ), abs( aimag( A( i, j ) ) ) ),
 *          i,j
 *
 * scale and sumsq must be supplied in SCALE and SUMSQ respectively.
 * SCALE and SUMSQ are overwritten by scl and ssq respectively.
 *
 * The routine makes only one pass through the tile triangular part of the
 * symmetric tile A defined by uplo.
 * See also LAPACK zlassq.f
 *
 *******************************************************************************
 *
 *  @param[in] uplo
 *          Specifies whether the upper or lower triangular part of
 *          the symmetric matrix A is to be referenced as follows:
 *          = PlasmaLower:     Only the lower triangular part of the
 *                             symmetric matrix A is to be referenced.
 *          = PlasmaUpper:     Only the upper triangular part of the
 *                             symmetric matrix A is to be referenced.
 *
 *  @param[in] N
 *          The number of columns and rows in the tile A.
 *
 *  @param[in] A
 *          The N-by-N matrix on which to compute the norm.
 *
 *  @param[in] LDA
 *          The leading dimension of the tile A. LDA >= max(1,N).
 *
 *  @param[in,out] scale
 *          On entry, the value  scale  in the equation above.
 *          On exit, scale is overwritten with the value scl.
 *
 *  @param[in,out] sumsq
 *          On entry, the value  sumsq  in the equation above.
 *          On exit, SUMSQ is overwritten with the value ssq.
 *
 *******************************************************************************
 *
 * @return
 *          \retval PLASMA_SUCCESS successful exit
 *          \retval -k, the k-th argument had an illegal value
 *
 */
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_zsyssq = PCORE_zsyssq
#define CORE_zsyssq PCORE_zsyssq
#endif
int CORE_zsyssq(PLASMA_enum uplo, int N,
                const PLASMA_Complex64_t *A, int LDA,
                double *scale, double *sumsq)
{
    int i, j;
    double tmp;
    double *ptr;

    if ( uplo == PlasmaUpper ) {
        for(j=0; j<N; j++) {
            ptr = (double*) ( A + j * LDA );

            for(i=0; i<j; i++, ptr++) {

                tmp = fabs(*ptr);
                UPDATE( 2., tmp );

#ifdef COMPLEX
                ptr++;
                tmp = fabs(*ptr);
                UPDATE( 2., tmp );
#endif
            }

            /* Diagonal */
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );

#ifdef COMPLEX
            ptr++;
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );
#endif
        }
    } else {

        for(j=0; j<N; j++) {
            ptr = (double*) ( A + j * LDA + j);

            /* Diagonal */
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );
            ptr++;

#ifdef COMPLEX
            tmp = fabs(*ptr);
            UPDATE( 1., tmp );
            ptr++;
#endif

            for(i=j+1; i<N; i++, ptr++) {

                tmp = fabs(*ptr);
                UPDATE( 2., tmp );

#ifdef COMPLEX
                ptr++;
                tmp = fabs(*ptr);
                UPDATE( 2., tmp );
#endif
            }
        }
    }
    return PLASMA_SUCCESS;
}


#ifndef PAHEMISSIONMODEL_H_
#define PAHEMISSIONMODEL_H_

#include "Exception.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <utility>
#include <vector>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_roots.h>

class PAHEmissionModel {

public:
  static double _energy;

  static double _frequency;

  static double solveInitialTemperatureFunc(double temperature,
                                            void *transitions);

  static double integralOverHeatCapacity(double temperature, void *transitions);

  static double heatCapacity(double temperature, void *transitions);

  static double featureStrength(double temperature, void *transitions);

  static void convertFromFrequencyToWavelength(std::vector<double> &grid);

  static void convertFromFrequencyToWavelength(std::array<double, 2> &grid);

  static void convertFromWavelengthToFrequency(std::vector<double> &grid);

  static void convertFromFrequencyToWavelength(
      std::vector<std::vector<std::pair<double, double>>> &transitions);

  static void convertFromWavelengthToFrequency(
      std::vector<std::vector<std::pair<double, double>>> &transitions);

  PAHEmissionModel();

  PAHEmissionModel(
      const std::vector<std::vector<std::pair<double, double>>> &transitions);

  void setTransitions(
      const std::vector<std::vector<std::pair<double, double>>> &transitions);

  void getTransitions(
      std::vector<std::vector<std::pair<double, double>>> &transitions);

  void shiftTransitions(double shift);

  void setGrid(const std::vector<double> &grid);

  void makeGrid(const std::vector<double> &frange, double step);

  void makeGrid(double fmin, double fmax, double step);

  std::vector<double> const &getGrid() const;

  void printTransitions();

  void applyCascadeWithEnergy(double energy, std::vector<double> &temperatures);

  void applyTemperatureWithEnergy(double energy,
                                  std::vector<double> &temperatures);

  void applyBlackbodyWithTemperature(double temperature);

  void
  applyBlackbodyWithTemperatureForEach(const std::vector<double> &temperatures);

  void getSpectraAndConvolveWithLorentianOfFHWM(
      std::vector<std::vector<double>> &vector, double fwhm = 15);

  void getSpectraAndConvolveWithGaussianOfFHWM(
      std::vector<std::vector<double>> &vector, double fwhm = 15);

  void
  getSpectraAndConvolveWithDrudeOfFHWM(std::vector<std::vector<double>> &vector,
                                       double fwhm = 15);

private:
  static constexpr double TemperatureMin = 2.73;

  static constexpr double TemperatureMax = 5000.0;

  static constexpr double RootAccuracy = 1e-4;

  static constexpr double IntegrationAccuracy = 1e-4;

  static constexpr int MaxIterations = 150;

  static constexpr int MaxSteps = 100;

  static constexpr double PlanckConstant = 6.6260693000000018e-27;

  static constexpr double SpeedOfLight = 29979245800.0;

  static constexpr double BoltzmannConstant = 1.3806504e-16;

  std::vector<std::vector<std::pair<double, double>>> _transitions;

  std::vector<double> _grid;

  double _fmin;

  double _fmax;

  double
  solveInitialTemperature(double energy,
                          std::vector<std::pair<double, double>> &transitions);

  double Lorentzian(double frequency, double centroid, double hwhm);

  double Gaussian(double frequency, double centroid, double sigma);

  double Drude(double frequency, double centroid, double fwhm);

  double Blackbody(double frequency, double temperature);
};

inline void PAHEmissionModel::makeGrid(const std::vector<double> &frange,
                                       double step) {
  makeGrid(frange[0], frange[1], step);
}

inline std::vector<double> const &PAHEmissionModel::getGrid() const {
  return (_grid);
}

inline double PAHEmissionModel::Lorentzian(double frequency, double centroid,
                                           double hwhm) {
  return ((1.0 / M_PI) * hwhm / (pow(frequency - centroid, 2) + pow(hwhm, 2)));
}

inline double PAHEmissionModel::Gaussian(double frequency, double centroid,
                                         double sigma) {
  return ((1.0 / (sigma * sqrt(2.0 * M_PI))) *
          exp(-pow(frequency - centroid, 2) / (2.0 * pow(sigma, 2))));
}

inline double PAHEmissionModel::Drude(double frequency, double centroid,
                                      double fwhm) {
  return ((2.0 / (fwhm * M_PI)) * pow(fwhm / centroid, 2) /
          (pow(centroid / frequency - frequency / centroid, 2) +
           pow(fwhm / centroid, 2)));
}

inline double PAHEmissionModel::Blackbody(double frequency,
                                          double temperature) {
  return (
      (2.0 * PlanckConstant * SpeedOfLight * SpeedOfLight * pow(frequency, 3)) /
      (exp(PlanckConstant * SpeedOfLight * frequency /
           (BoltzmannConstant * temperature)) -
       1.0));
}

#endif /* PAHEMISSIONMODEL_H_ */


#include <assert.h>
#include <time.h>
#include <stdlib.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_rng.h>
#include "utils/utils.h"
#include "utils/compress.h"
#include "rule.h"

#define DIRICHLET 1
#define D_CONST .4

void populate_buf(uint64_t grule_size, uint8_t* rule_array, char* rule_buf)
{
  /* Populate buffer with newly created rule */
  for (uint64_t v = 0; v < grule_size; v++) {
    sprintf(&rule_buf[v], "%"PRIu8, rule_array[v]);
  }
  rule_buf[grule_size] = '\0';
}

/**
 * Symmetrize the rule by setting all the states and their symmetries to having
 * the same output.
 */
void symmetrize_rule(uint64_t grule_size,
                     uint8_t rule_array[grule_size],
                     int states, int horizon)
{
  int side = 2 * horizon + 1;
  int neigh_size = side * side - 1;

  uint32_t position_180;
  uint32_t position_90;
  uint32_t position_270;
  uint32_t position_vflip;
  uint32_t position_hflip;
  uint32_t position_dflip;
  uint32_t position_aflip;

  /* grule_size can be very big, this array is better on the heap */
  /* Keep track of already seen positions with book-keeping */
  uint8_t* book_keep = calloc(grule_size, sizeof(uint8_t));

  int pos;

  for (uint64_t i = 0; i < grule_size; ++i) {
    /* Skip already seen positions when looping through the rule */
    if (book_keep[i] == 1) {
      continue;
    }

    position_180 = 0;
    position_90 = 0;
    position_270 = 0;
    position_vflip = 0;
    position_hflip = 0;
    position_dflip = 0;
    position_aflip = 0;

    /* Create the representation of the symmetrized position by swapping the
       states in its number representation. */
    for (int p = 0; p < neigh_size + 1; ++p) {
      /* 180° rotation */
      position_180 += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, neigh_size  - p)) % states);

      /* 90° rotation */
      pos = (neigh_size - side + 1 - (side * (p%side)) + p/side);
      position_90 += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* 270° rotation */
      position_270 += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, neigh_size  - pos)) % states);

      /* Vertical flip */
      pos = (side * (p / side)) + (side - 1 - (p % 3));
      position_vflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* Horizontal flip */
      if (p/side < side/2) {
        pos = (p - side + neigh_size + 1)%(neigh_size + 1);
      } else if (p/side > side/2) {
        pos = (p + side)%(neigh_size + 1);
      } else {
        pos = p;
      }
      position_hflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* Diagonal flip */
      pos = neigh_size - ((p % side) * side  + (p / side));
      position_dflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

      /* Antidiagonal flip */
      pos = ((p % side) * side  + (p / side));
      position_aflip += (uint32_t)ipow(states, p) *
        ((i / (uint32_t)ipow(states, pos)) % states);

    }

    /* Add all seen positions to the book to not process them again */
    book_keep[i] = 1;
    book_keep[position_180] = 1;
    book_keep[position_90] = 1;
    book_keep[position_270] = 1;
    book_keep[position_vflip] = 1;
    book_keep[position_hflip] = 1;
    book_keep[position_dflip] = 1;
    book_keep[position_aflip] = 1;

    rule_array[position_180] = rule_array[i];
    rule_array[position_90] = rule_array[i];
    rule_array[position_270] = rule_array[i];
    rule_array[position_vflip] = rule_array[i];
    rule_array[position_hflip] = rule_array[i];
    rule_array[position_dflip] = rule_array[i];
    rule_array[position_aflip] = rule_array[i];
  }
  free(book_keep);
}

/**
 * @brief Build a rule from the provided command-line arguments.
 * @param grule_size The size of the rule.
 * @param rule_array The array holding the rule.
 * @param rule_buf A mirror buffer containing a string representation of the
 *                 rule.
 * @param rule_arg The string from which the rule is read.
 * @param states Number of expected states in the rule.
 */
void build_rule_from_args(uint64_t grule_size,
                          uint8_t rule_array[grule_size],
                          char rule_buf[grule_size + 1],
                          char* rule_arg, int states)
{
  /* Rule is given in base-(states - 1) format */
  for (uint64_t s = 0 ; s < grule_size; ++s) {
    rule_array[s] = rule_arg[s] - '0';
    rule_buf[s] = rule_arg[s];
    assert(rule_array[s] < states);
  }
  rule_buf[grule_size] = '\0';
}

/**
 * Simple double comparison function used for sorting in the rule generation
 * function.
 */
int comp (const void * elem1, const void * elem2)
{
  float f = *((float*)elem1);
  float s = *((float*)elem2);
  return (f > s) - (f < s);
}

/**
 * Generate a random general rule. The rule is written to rule_array and a
 * string  representation is also saved in rule_buf.
 */
void generate_general_rule(uint64_t grule_size,
                           uint8_t rule_array[grule_size],
                           char rule_buf[grule_size + 1],
                           int states, int horizon)
{
  int inc;

  /* Choose lambda parameter at random as well as the proportion of
     transitions to other states */
  double alphas[states];
  double theta[states], lambda[states - 1], rand_num;

  /* This method samples the transition probability simplex according to a
     Dirichlet distribution with parameter D_CONST */
  if (DIRICHLET == 1) {
    const gsl_rng_type * T;

    gsl_rng * r;
    gsl_rng_env_setup();

    T = gsl_rng_default;
    r = gsl_rng_alloc(T);
    time_t t;
    gsl_rng_set(r, (unsigned long)time(&t));

    for (int i = 0; i < states; ++i) {
      alphas[i] = D_CONST;
    }
    gsl_ran_dirichlet(r, states, alphas, theta);
    lambda[0] = theta[0];
    for (int i = 1; i < states - 1; ++i) {
      lambda[i] = theta[i] + lambda[i - 1];
    }

    gsl_rng_free(r);
  }
  /* Second method that samples the simplex uniformly */
  else {
    for (int i = 0; i < states - 1; ++i) {
      lambda[i] = ((double)rand() / (double)((unsigned)RAND_MAX + 1));
    }
    qsort(lambda, sizeof(lambda)/sizeof(*lambda), sizeof(*lambda), comp);
  }

  for (uint64_t v = 0; v < grule_size; v++) {
    /* Assign the rule to the first state that passes the test */
    rand_num = (double)rand() / (double)((unsigned)RAND_MAX + 1);
    inc = 0;
    while (lambda[inc] < rand_num && inc < states - 1) {
      ++inc;
    }
    rule_array[v] = (uint8_t)inc;
  }

  symmetrize_rule(grule_size, rule_array, states, horizon);

  populate_buf(grule_size, rule_array, rule_buf);
}

void cross_breed(uint64_t grule_size, uint8_t* parent_rule_A,
                 uint8_t* parent_rule_B, uint8_t* child,
                 char rule_buf[grule_size], double rate,
                 int horizon, int states)
{
  for (uint64_t i = 0; i < grule_size; ++i) {
    double rand_num = (double)rand() / (double)((unsigned)RAND_MAX + 1);
    child[i] = (rate > rand_num) ? parent_rule_A[i]: parent_rule_B[i];
  }

  perturb_rule(grule_size, child, rule_buf, states, horizon, 0.05);
}

void perturb_rule(uint64_t grule_size,
                  uint8_t rule_array[grule_size],
                  char rule_buf[grule_size + 1],
                  int states, int horizon, double rate)
{
  for (uint64_t v = 0; v < grule_size; ++v) {
    /* Perturb transisition outcome with probability rate */
    double rand_num = (double)rand() / (double)((unsigned)RAND_MAX + 1);
    if (rand_num < rate) {
      rule_array[v] += (1 + rand() % (states - 1));
      rule_array[v] %= states;
    }
  }

  symmetrize_rule(grule_size, rule_array, states, horizon);
  populate_buf(grule_size, rule_array, rule_buf);
}

void make_map(struct Options2D* opts, char* rule_buf, int step)
{
  FILE* dic_file;
  char* fname;

  asprintf(&fname, "%s/map/%lu.map", opts->data_dir_name, hash(rule_buf));
  dic_file = fopen(fname, "w+");

  if (dic_file == NULL) {
    fprintf(stderr, "Error while opening map file %s.", fname);
    exit(1);
  }

  fprintf(dic_file, "%s", rule_buf);
  fclose(dic_file);
  free(fname);

  sprintf(rule_buf, "%lu", hash(rule_buf));
  printf("%i: Rule %s\n", step, rule_buf);
}

void generate_totalistic_rule(uint64_t rule_size, uint8_t rule_array[rule_size],
                              char rule_buf[rule_size + 1], int states)
{
  unsigned long rule_number = 0UL;

  for (uint64_t s = 0 ; s < rule_size; ++s) {
    rule_array[s] = rand() % states;
    rule_number += rule_array[s] * ipow(states, s);
    if (states >= 3) {
      rule_buf[s] = '0' + rule_array[s];
    }
  }

  if (states == 2) {
    sprintf(rule_buf, "%lu", rule_number);
  }
}


/***************************************************************************
 * data_cf.h is part of Math Graphic Library
 * Copyright (C) 2007-2016 Alexey Balakin <mathgl.abalakin@gmail.ru>       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifndef _MGL_DATAC_CF_H_
#define _MGL_DATAC_CF_H_
//-----------------------------------------------------------------------------
#include "mgl2/abstract.h"
//-----------------------------------------------------------------------------
#if MGL_HAVE_GSL
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#else
struct gsl_vector;
struct gsl_matrix;
#endif
//-----------------------------------------------------------------------------
#ifdef __cplusplus
class mglDataC;
typedef mglDataC* HADT;
extern "C" {
#else
typedef void *HADT;
#endif
/// Get integer power of x
cmdual MGL_EXPORT_CONST mgl_ipowc(mdual x,int n);
cmdual MGL_EXPORT mgl_ipowc_(mdual *x,int *n);
/// Get complex number from string. Parse (%g,%g), {%g,%g} and [%g,%g] if adv!=0.
cmdual MGL_EXPORT mgl_atoc(const char *s, int adv);
/// Get exp(i*a)
cmdual MGL_EXPORT_CONST mgl_expi(mdual a);

/// Create HMDT object
HADT MGL_EXPORT mgl_create_datac();
uintptr_t MGL_EXPORT mgl_create_datac_();
/// Create HMDT object with specified sizes
HADT MGL_EXPORT mgl_create_datac_size(long nx, long ny, long nz);
uintptr_t MGL_EXPORT mgl_create_datac_size_(int *nx, int *ny, int *nz);
/// Create HMDT object with data from file
HADT MGL_EXPORT mgl_create_datac_file(const char *fname);
uintptr_t MGL_EXPORT mgl_create_datac_file_(const char *fname, int len);
/// Delete HMDT object
void MGL_EXPORT mgl_delete_datac(HADT dat);
void MGL_EXPORT mgl_delete_datac_(uintptr_t *dat);

/// Rearange data dimensions
void MGL_EXPORT mgl_datac_rearrange(HADT dat, long mx,long my,long mz);
void MGL_EXPORT mgl_datac_rearrange_(uintptr_t *dat, int *mx, int *my, int *mz);
/// Link external data array (don't delete it at exit)
void MGL_EXPORT mgl_datac_link(HADT dat, mdual *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_link_(uintptr_t *d, mdual *A, int *nx,int *ny,int *nz);
/// Allocate memory and copy the data from the (float *) array
void MGL_EXPORT mgl_datac_set_float(HADT dat, const float *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_set_float_(uintptr_t *dat, const float *A,int *NX,int *NY,int *NZ);
/// Allocate memory and copy the data from the (double *) array
void MGL_EXPORT mgl_datac_set_double(HADT dat, const double *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_set_double_(uintptr_t *dat, const double *A,int *NX,int *NY,int *NZ);
/// Allocate memory and copy the data from the (dual *) array
void MGL_EXPORT mgl_datac_set_complex(HADT dat, const mdual *A,long mx,long my,long mz);
void MGL_EXPORT mgl_datac_set_complex_(uintptr_t *d, const mdual *A,int *NX,int *NY,int *NZ);
/// Import data from abstract type
void MGL_EXPORT mgl_datac_set(HADT dat, HCDT a);
void MGL_EXPORT mgl_datac_set_(uintptr_t *dat, uintptr_t *a);
/// Allocate memory and copy the data from the gsl_vector
void MGL_EXPORT mgl_datac_set_vector(HADT dat, gsl_vector *v);
/// Allocate memory and copy the data from the gsl_matrix
void MGL_EXPORT mgl_datac_set_matrix(HADT dat, gsl_matrix *m);
/// Set value of data element [i,j,k]
void MGL_EXPORT mgl_datac_set_value(HADT dat, mdual v, long i, long j, long k);
void MGL_EXPORT mgl_datac_set_value_(uintptr_t *d, mdual *v, int *i, int *j, int *k);
/// Get value of data element [i,j,k]
cmdual MGL_EXPORT mgl_datac_get_value(HCDT dat, long i, long j, long k);
cmdual MGL_EXPORT mgl_datac_get_value_(uintptr_t *d, int *i, int *j, int *k);
/// Allocate memory and scanf the data from the string
void MGL_EXPORT mgl_datac_set_values(HADT dat, const char *val, long nx, long ny, long nz);
void MGL_EXPORT mgl_datac_set_values_(uintptr_t *d, const char *val, int *nx, int *ny, int *nz, int l);

/// Get array as solution of tridiagonal matrix solution a[i]*x[i-1]+b[i]*x[i]+c[i]*x[i+1]=d[i]
/** String \a how may contain:
 * 'x', 'y', 'z' for solving along x-,y-,z-directions, or
 * 'h' for solving along hexagonal direction at x-y plain (need nx=ny),
 * 'c' for using periodical boundary conditions,
 * 'd' for diffraction/diffuse calculation.
 * NOTE: It work for flat data model only (i.e. for a[i,j]==a[i+nx*j]) */
HADT MGL_EXPORT mgl_datac_tridmat(HCDT A, HCDT B, HCDT C, HCDT D, const char *how);
uintptr_t MGL_EXPORT mgl_datac_tridmat_(uintptr_t *A, uintptr_t *B, uintptr_t *C, uintptr_t *D, const char *how, int);

/// Returns pointer to internal data array
MGL_EXPORT mdual *mgl_datac_data(HADT dat);
/// Returns pointer to data element [i,j,k]
MGL_EXPORT mdual *mgl_datac_value(HADT dat, long i,long j,long k);

/// Set the data from HCDT objects for real and imaginary parts
void MGL_EXPORT mgl_datac_set_ri(HADT dat, HCDT re, HCDT im);
void MGL_EXPORT mgl_datac_set_ri_(uintptr_t *dat, uintptr_t *re, uintptr_t *im);
/// Set the data from HCDT objects as amplitude and phase of complex data
void MGL_EXPORT mgl_datac_set_ap(HADT dat, HCDT abs, HCDT phi);
void MGL_EXPORT mgl_datac_set_ap_(uintptr_t *dat, uintptr_t *abs, uintptr_t *phi);

/// Read data from tab-separated text file with auto determining size
int MGL_EXPORT mgl_datac_read(HADT dat, const char *fname);
int MGL_EXPORT mgl_datac_read_(uintptr_t *d, const char *fname,int l);
/// Read data from text file with size specified at beginning of the file
int MGL_EXPORT mgl_datac_read_mat(HADT dat, const char *fname, long dim);
int MGL_EXPORT mgl_datac_read_mat_(uintptr_t *dat, const char *fname, int *dim, int);
/// Read data from text file with specifeid size
int MGL_EXPORT mgl_datac_read_dim(HADT dat, const char *fname,long mx,long my,long mz);
int MGL_EXPORT mgl_datac_read_dim_(uintptr_t *dat, const char *fname,int *mx,int *my,int *mz,int);
/// Read data from tab-separated text files with auto determining size which filenames are result of sprintf(fname,templ,t) where t=from:step:to
int MGL_EXPORT mgl_datac_read_range(HADT d, const char *templ, double from, double to, double step, int as_slice);
int MGL_EXPORT mgl_datac_read_range_(uintptr_t *d, const char *fname, mreal *from, mreal *to, mreal *step, int *as_slice,int l);
/// Read data from tab-separated text files with auto determining size which filenames are satisfied to template (like "t_*.dat")
int MGL_EXPORT mgl_datac_read_all(HADT dat, const char *templ, int as_slice);
int MGL_EXPORT mgl_datac_read_all_(uintptr_t *d, const char *fname, int *as_slice,int l);
/// Save whole data array (for ns=-1) or only ns-th slice to text file
void MGL_EXPORT mgl_datac_save(HCDT dat, const char *fname,long ns);
void MGL_EXPORT mgl_datac_save_(uintptr_t *dat, const char *fname,int *ns,int);

/// Read data array from HDF file (parse HDF4 and HDF5 files)
int MGL_EXPORT mgl_datac_read_hdf(HADT d,const char *fname,const char *data);
int MGL_EXPORT mgl_datac_read_hdf_(uintptr_t *d, const char *fname, const char *data,int l,int n);
/// Save data to HDF file
void MGL_EXPORT mgl_datac_save_hdf(HCDT d,const char *fname,const char *data,int rewrite);
void MGL_EXPORT mgl_datac_save_hdf_(uintptr_t *d, const char *fname, const char *data, int *rewrite,int l,int n);

/// Create or recreate the array with specified size and fill it by zero
void MGL_EXPORT mgl_datac_create(HADT dat, long nx,long ny,long nz);
void MGL_EXPORT mgl_datac_create_(uintptr_t *dat, int *nx,int *ny,int *nz);
/// Transpose dimensions of the data (generalization of Transpose)
void MGL_EXPORT mgl_datac_transpose(HADT dat, const char *dim);
void MGL_EXPORT mgl_datac_transpose_(uintptr_t *dat, const char *dim,int);

/// Get sub-array of the data with given fixed indexes
HADT MGL_EXPORT mgl_datac_subdata(HCDT dat, long xx,long yy,long zz);
uintptr_t MGL_EXPORT mgl_datac_subdata_(uintptr_t *dat, int *xx,int *yy,int *zz);
/// Get sub-array of the data with given fixed indexes (like indirect access)
HADT MGL_EXPORT mgl_datac_subdata_ext(HCDT dat, HCDT xx, HCDT yy, HCDT zz);
uintptr_t MGL_EXPORT mgl_datac_subdata_ext_(uintptr_t *dat, uintptr_t *xx,uintptr_t *yy,uintptr_t *zz);
/// Get column (or slice) of the data filled by formulas of named columns
HADT MGL_EXPORT mgl_datac_column(HCDT dat, const char *eq);
uintptr_t MGL_EXPORT mgl_datac_column_(uintptr_t *dat, const char *eq,int l);
/// Get trace of the data array
HADT MGL_EXPORT mgl_datac_trace(HCDT d);
uintptr_t MGL_EXPORT mgl_datac_trace_(uintptr_t *d);
/// Resize the data to new sizes
HADT MGL_EXPORT mgl_datac_resize(HCDT dat, long mx,long my,long mz);
uintptr_t MGL_EXPORT mgl_datac_resize_(uintptr_t *dat, int *mx,int *my,int *mz);
/// Resize the data to new sizes of box [x1,x2]*[y1,y2]*[z1,z2]
HADT MGL_EXPORT mgl_datac_resize_box(HCDT dat, long mx,long my,long mz,mreal x1,mreal x2,mreal y1,mreal y2,mreal z1,mreal z2);
uintptr_t MGL_EXPORT mgl_datac_resize_box_(uintptr_t *dat, int *mx,int *my,int *mz,mreal *x1,mreal *x2,mreal *y1,mreal *y2,mreal *z1,mreal *z2);
/// Get momentum (1D-array) of data along direction 'dir'. String looks like "x1" for median in x-direction, "x2" for width in x-dir and so on.
HADT MGL_EXPORT mgl_datac_momentum(HCDT dat, char dir, const char *how);
uintptr_t MGL_EXPORT mgl_datac_momentum_(uintptr_t *dat, char *dir, const char *how, int,int);
/// Get array which values is result of interpolation this for coordinates from other arrays
HADT MGL_EXPORT mgl_datac_evaluate(HCDT dat, HCDT idat, HCDT jdat, HCDT kdat, int norm);
uintptr_t MGL_EXPORT mgl_datac_evaluate_(uintptr_t *dat, uintptr_t *idat, uintptr_t *jdat, uintptr_t *kdat, int *norm);
/// Get array which is result of summation in given direction or directions
HADT MGL_EXPORT mgl_datac_sum(HCDT dat, const char *dir);
uintptr_t MGL_EXPORT mgl_datac_sum_(uintptr_t *dat, const char *dir,int);
/// Get the data which is direct multiplication (like, d[i,j] = this[i]*a[j] and so on)
HADT MGL_EXPORT mgl_datac_combine(HCDT dat1, HCDT dat2);
uintptr_t MGL_EXPORT mgl_datac_combine_(uintptr_t *dat1, uintptr_t *dat2);
/// Get data from sections ids, separated by value val along specified direction.
/** If section id is negative then reverse order is used (i.e. -1 give last section). */
HADT MGL_EXPORT mgl_datac_section(HCDT dat, HCDT ids, char dir, mreal val);
uintptr_t MGL_EXPORT mgl_datac_section_(uintptr_t *d, uintptr_t *ids, const char *dir, mreal *val,int);
/// Get data from section id, separated by value val along specified direction.
/** If section id is negative then reverse order is used (i.e. -1 give last section). */
HADT MGL_EXPORT mgl_datac_section_val(HCDT dat, long id, char dir, mreal val);
uintptr_t MGL_EXPORT mgl_datac_section_val_(uintptr_t *d, int *id, const char *dir, mreal *val,int);

/// Equidistantly fill the data to range [x1,x2] in direction dir
void MGL_EXPORT mgl_datac_fill(HADT dat, mdual x1,mdual x2,char dir);
void MGL_EXPORT mgl_datac_fill_(uintptr_t *dat, mdual *x1,mdual *x2,const char *dir,int);
/// Modify the data by specified formula assuming x,y,z in range [r1,r2]
void MGL_EXPORT mgl_datac_fill_eq(HMGL gr, HADT dat, const char *eq, HCDT vdat, HCDT wdat,const char *opt);
void MGL_EXPORT mgl_datac_fill_eq_(uintptr_t *gr, uintptr_t *dat, const char *eq, uintptr_t *vdat, uintptr_t *wdat,const char *opt, int, int);
/// Fill dat by interpolated values of vdat parametrically depended on xdat for x in range [x1,x2] using global spline
void MGL_EXPORT mgl_datac_refill_gs(HADT dat, HCDT xdat, HCDT vdat, mreal x1, mreal x2, long sl);
void MGL_EXPORT mgl_datac_refill_gs_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *vdat, mreal *x1, mreal *x2, long *sl);
/// Fill dat by interpolated values of vdat parametrically depended on xdat for x in range [x1,x2]
void MGL_EXPORT mgl_datac_refill_x(HADT dat, HCDT xdat, HCDT vdat, mreal x1, mreal x2, long sl);
void MGL_EXPORT mgl_datac_refill_x_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *vdat, mreal *x1, mreal *x2, long *sl);
/// Fill dat by interpolated values of vdat parametrically depended on xdat,ydat for x,y in range [x1,x2]*[y1,y2]
void MGL_EXPORT mgl_datac_refill_xy(HADT dat, HCDT xdat, HCDT ydat, HCDT vdat, mreal x1, mreal x2, mreal y1, mreal y2, long sl);
void MGL_EXPORT mgl_datac_refill_xy_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *ydat, uintptr_t *vdat, mreal *x1, mreal *x2, mreal *y1, mreal *y2, long *sl);
/// Fill dat by interpolated values of vdat parametrically depended on xdat,ydat,zdat for x,y,z in range [x1,x2]*[y1,y2]*[z1,z2]
void MGL_EXPORT mgl_datac_refill_xyz(HADT dat, HCDT xdat, HCDT ydat, HCDT zdat, HCDT vdat, mreal x1, mreal x2, mreal y1, mreal y2, mreal z1, mreal z2);
void MGL_EXPORT mgl_datac_refill_xyz_(uintptr_t *dat, uintptr_t *xdat, uintptr_t *ydat, uintptr_t *zdat, uintptr_t *vdat, mreal *x1, mreal *x2, mreal *y1, mreal *y2, mreal *z1, mreal *z2);
/// Fill dat by interpolated values of vdat parametrically depended on xdat,ydat,zdat for x,y,z in axis range
void MGL_EXPORT mgl_datac_refill_gr(HMGL gr, HADT dat, HCDT xdat, HCDT ydat, HCDT zdat, HCDT vdat, long sl, const char *opt);
void MGL_EXPORT mgl_datac_refill_gr_(uintptr_t *gr, uintptr_t *dat, uintptr_t *xdat, uintptr_t *ydat, uintptr_t *zdat, uintptr_t *vdat, long *sl, const char *opt,int);

/// Modify the data by specified formula
void MGL_EXPORT mgl_datac_modify(HADT dat, const char *eq,long dim);
void MGL_EXPORT mgl_datac_modify_(uintptr_t *dat, const char *eq,int *dim,int);
/// Modify the data by specified formula
void MGL_EXPORT mgl_datac_modify_vw(HADT dat, const char *eq,HCDT vdat,HCDT wdat);
void MGL_EXPORT mgl_datac_modify_vw_(uintptr_t *dat, const char *eq, uintptr_t *vdat, uintptr_t *wdat,int);

/// Limit the data to be inside [-v,v], keeping the original sign
void MGL_EXPORT mgl_datac_limit(HADT dat, mreal v);
void MGL_EXPORT mgl_datac_limit_(uintptr_t *dat, mreal *v);

/// Put value to data element(s)
void MGL_EXPORT mgl_datac_put_val(HADT dat, mdual val, long i, long j, long k);
void MGL_EXPORT mgl_datac_put_val_(uintptr_t *dat, mdual *val, int *i, int *j, int *k);
/// Put array to data element(s)
void MGL_EXPORT mgl_datac_put_dat(HADT dat, HCDT val, long i, long j, long k);
void MGL_EXPORT mgl_datac_put_dat_(uintptr_t *dat, uintptr_t *val, int *i, int *j, int *k);

/// Reduce size of the data
void MGL_EXPORT mgl_datac_squeeze(HADT dat, long rx,long ry,long rz,long smooth);
void MGL_EXPORT mgl_datac_squeeze_(uintptr_t *dat, int *rx,int *ry,int *rz,int *smooth);
/// Extend data dimensions
void MGL_EXPORT mgl_datac_extend(HADT dat, long n1, long n2);
void MGL_EXPORT mgl_datac_extend_(uintptr_t *dat, int *n1, int *n2);
/// Insert data rows/columns/slices
void MGL_EXPORT mgl_datac_insert(HADT dat, char dir, long at, long num);
void MGL_EXPORT mgl_datac_insert_(uintptr_t *dat, const char *dir, int *at, int *num, int);
/// Delete data rows/columns/slices
void MGL_EXPORT mgl_datac_delete(HADT dat, char dir, long at, long num);
void MGL_EXPORT mgl_datac_delete_(uintptr_t *dat, const char *dir, int *at, int *num, int);
/// Joind another data array
void MGL_EXPORT mgl_datac_join(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_join_(uintptr_t *dat, uintptr_t *d);

/// Smooth the data on specified direction or directions
/** String \a dir may contain:
 *  ‘x’, ‘y’, ‘z’ for 1st, 2nd or 3d dimension;
 *  ‘dN’ for linear averaging over N points;
 *  ‘3’ for linear averaging over 3 points;
 *  ‘5’ for linear averaging over 5 points.
 *  By default quadratic averaging over 5 points is used. */
void MGL_EXPORT mgl_datac_smooth(HADT d, const char *dirs, mreal delta);
void MGL_EXPORT mgl_datac_smooth_(uintptr_t *dat, const char *dirs, mreal *delta,int);
/// Cumulative summation the data in given direction or directions
void MGL_EXPORT mgl_datac_cumsum(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_cumsum_(uintptr_t *dat, const char *dir,int);
/// Integrate (cumulative summation) the data in given direction or directions
void MGL_EXPORT mgl_datac_integral(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_integral_(uintptr_t *dat, const char *dir,int);
/// Differentiate the data in given direction or directions
void MGL_EXPORT mgl_datac_diff(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_diff_(uintptr_t *dat, const char *dir,int);
/// Differentiate the parametrically specified data along direction v1 with v2,v3=const (v3 can be NULL)
void MGL_EXPORT mgl_datac_diff_par(HADT dat, HCDT v1, HCDT v2, HCDT v3);
void MGL_EXPORT mgl_datac_diff_par_(uintptr_t *dat, uintptr_t *v1, uintptr_t *v2, uintptr_t *v3);
/// Double-differentiate (like Laplace operator) the data in given direction
void MGL_EXPORT mgl_datac_diff2(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_diff2_(uintptr_t *dat, const char *dir,int);
/// Swap left and right part of the data in given direction (useful for Fourier spectrum)
void MGL_EXPORT mgl_datac_swap(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_swap_(uintptr_t *dat, const char *dir,int);
/// Roll data along direction dir by num slices
void MGL_EXPORT mgl_datac_roll(HADT dat, char dir, long num);
void MGL_EXPORT mgl_datac_roll_(uintptr_t *dat, const char *dir, int *num, int);
/// Mirror the data in given direction (useful for Fourier spectrum)
void MGL_EXPORT mgl_datac_mirror(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_mirror_(uintptr_t *dat, const char *dir,int);
/// Crop the data
void MGL_EXPORT mgl_datac_crop(HADT dat, long n1, long n2, char dir);
void MGL_EXPORT mgl_datac_crop_(uintptr_t *dat, int *n1, int *n2, const char *dir,int);
/// Crop the data to be most optimal for FFT (i.e. to closest value of 2^n*3^m*5^l)
void MGL_EXPORT mgl_datac_crop_opt(HADT dat, const char *how);
void MGL_EXPORT mgl_datac_crop_opt_(uintptr_t *dat, const char *how,int);

/// Multiply the data by other one for each element
void MGL_EXPORT mgl_datac_mul_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_mul_dat_(uintptr_t *dat, uintptr_t *d);
/// Divide the data by other one for each element
void MGL_EXPORT mgl_datac_div_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_div_dat_(uintptr_t *dat, uintptr_t *d);
/// Add the other data
void MGL_EXPORT mgl_datac_add_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_add_dat_(uintptr_t *dat, uintptr_t *d);
/// Subtract the other data
void MGL_EXPORT mgl_datac_sub_dat(HADT dat, HCDT d);
void MGL_EXPORT mgl_datac_sub_dat_(uintptr_t *dat, uintptr_t *d);
/// Multiply each element by the number
void MGL_EXPORT mgl_datac_mul_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_mul_num_(uintptr_t *dat, mdual *d);
/// Divide each element by the number
void MGL_EXPORT mgl_datac_div_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_div_num_(uintptr_t *dat, mdual *d);
/// Add the number
void MGL_EXPORT mgl_datac_add_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_add_num_(uintptr_t *dat, mdual *d);
/// Subtract the number
void MGL_EXPORT mgl_datac_sub_num(HADT dat, mdual d);
void MGL_EXPORT mgl_datac_sub_num_(uintptr_t *dat, mdual *d);

/// Apply Hankel transform
void MGL_EXPORT mgl_datac_hankel(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_hankel_(uintptr_t *dat, const char *dir,int);
/// Apply Sin-Fourier transform
void MGL_EXPORT mgl_datac_sinfft(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_sinfft_(uintptr_t *dat, const char *dir,int);
/// Apply Cos-Fourier transform
void MGL_EXPORT mgl_datac_cosfft(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_cosfft_(uintptr_t *dat, const char *dir,int);
/// Apply Fourier transform
void MGL_EXPORT mgl_datac_fft(HADT dat, const char *dir);
void MGL_EXPORT mgl_datac_fft_(uintptr_t *dat, const char *dir,int);
/// Find correlation between 2 data arrays
HADT MGL_EXPORT mgl_datac_correl(HCDT dat1, HCDT dat2, const char *dir);
uintptr_t MGL_EXPORT mgl_datac_correl_(uintptr_t *dat1, uintptr_t *dat2, const char *dir,int);
/// Calculate one step of diffraction by finite-difference method with parameter q
void MGL_EXPORT mgl_datac_diffr(HADT dat, const char *how, mreal q);
void MGL_EXPORT mgl_datac_diffr_(uintptr_t *d, const char *how, double q,int l);
/// Apply wavelet transform
/** Parameter \a dir may contain:
 * ‘x‘,‘y‘,‘z‘ for directions,
 * ‘d‘ for daubechies, ‘D‘ for centered daubechies,
 * ‘h‘ for haar, ‘H‘ for centered haar,
 * ‘b‘ for bspline, ‘B‘ for centered bspline,
 * ‘i‘ for applying inverse transform. */
void MGL_EXPORT mgl_datac_wavelet(HADT dat, const char *how, int k);
void MGL_EXPORT mgl_datac_wavelet_(uintptr_t *d, const char *dir, int *k,int);
/// Set as the data envelop
void MGL_EXPORT mgl_datac_envelop(HADT dat, char dir);
void MGL_EXPORT mgl_datac_envelop_(uintptr_t *dat, const char *dir, int);

/// Get real part of data values
HMDT MGL_EXPORT mgl_datac_real(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_real_(uintptr_t *dat);
/// Get imaginary part of data values
HMDT MGL_EXPORT mgl_datac_imag(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_imag_(uintptr_t *dat);
/// Get absolute value of data values, i.e. |u|
HMDT MGL_EXPORT mgl_datac_abs(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_abs_(uintptr_t *dat);
/// Get argument of data values
HMDT MGL_EXPORT mgl_datac_arg(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_arg_(uintptr_t *dat);
/// Get square of absolute value of data values, i.e. |u|^2
HMDT MGL_EXPORT mgl_datac_norm(HCDT dat);
uintptr_t MGL_EXPORT mgl_datac_norm_(uintptr_t *dat);

/// Interpolate by linear function the data to given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_linear(HCDT d, mreal x,mreal y,mreal z);
cmdual MGL_EXPORT mgl_datac_linear_(uintptr_t *d, mreal *x,mreal *y,mreal *z);
/// Interpolate by linear function the data and return its derivatives at given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_linear_ext(HCDT d, mreal x,mreal y,mreal z, mdual *dx,mdual *dy,mdual *dz);
cmdual MGL_EXPORT mgl_datac_linear_ext_(uintptr_t *d, mreal *x,mreal *y,mreal *z, mdual *dx,mdual *dy,mdual *dz);
/// Interpolate by cubic spline the data to given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_spline(HCDT dat, mreal x,mreal y,mreal z);
cmdual MGL_EXPORT mgl_datac_spline_(uintptr_t *dat, mreal *x,mreal *y,mreal *z);
/// Interpolate by cubic spline the data and return its derivatives at given point x=[0...nx-1], y=[0...ny-1], z=[0...nz-1]
cmdual MGL_EXPORT mgl_datac_spline_ext(HCDT dat, mreal x,mreal y,mreal z, mdual *dx,mdual *dy,mdual *dz);
cmdual MGL_EXPORT mgl_datac_spline_ext_(uintptr_t *dat, mreal *x,mreal *y,mreal *z, mdual *dx,mdual *dy,mdual *dz);
/// Prepare coefficients for global spline interpolation
HADT MGL_EXPORT mgl_gsplinec_init(HCDT x, HCDT v);
uintptr_t MGL_EXPORT mgl_gspline_init_(uintptr_t *x, uintptr_t *v);
/// Evaluate global spline (and its derivatives d1, d2 if not NULL) using prepared coefficients \a coef
cmdual MGL_EXPORT mgl_gsplinec(HCDT coef, mreal dx, mdual *d1, mdual *d2);
cmdual MGL_EXPORT mgl_gsplinec_(uintptr_t *c, mreal *dx, mdual *d1, mdual *d2);

/// Find roots for set of nonlinear equations defined by textual formulas
HADT MGL_EXPORT mgl_find_roots_txt_c(const char *func, const char *vars, HCDT ini);
uintptr_t MGL_EXPORT mgl_find_roots_txt_c_(const char *func, const char *vars, uintptr_t *ini,int,int);

#ifdef __cplusplus
}
#endif
//-----------------------------------------------------------------------------
#endif