/*
* -----------------------------------------------------------------
*  pmsr_lib.c
*  Pairwise Mixing Stirred Reactor Library
*  Version: 2.0
*  Last Update: Oct 1, 2019
*
*  This is a computational library with routines to implement
*  a Pairwise Mixing Stirred Reactor (PMSR) model.
* -----------------------------------------------------------------
*  Programmer: Americo Barbosa da Cunha Junior
*              americo.cunhajr@gmail.com
* -----------------------------------------------------------------
*  Copyright (c) 2019 by Americo Barbosa da Cunha Junior
* -----------------------------------------------------------------
*/

#include <math.h>
#include <time.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_errno.h>

#include "../include/pmsr_lib.h"

/*
* -----------------------------------------------------------------
*   pmsr_title
*
*   This function prints the program title on the screen.
*
*   last update: Oct 20, 2009
* -----------------------------------------------------------------
*/

void pmsr_title()
{
    time_t curtime = time (NULL);
    struct tm *loctime = localtime (&curtime);

    printf("\n");
    printf("=============================================");
    printf("\n Pairwise Mixing Stirred Reactor --- PMSR\n");
    printf("\n by");
    printf("\n Americo Barbosa da Cunha Junior");
    printf("\n americo.cunhajr@gmail.com\n");
    printf("\n Date:");
    printf("\n %s", asctime(loctime) );
    printf("=============================================");
    printf("\n");

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_input
*
*   This function receives PMSR parameters from user
*   and returns GSL_SUCCESS if there is no error.
*
*   Input:
*   seed         - RNG seed
*   Np           - # of particles
*   Ndt          - # of time steps
*   t0           - initial time
*   delta_t      - time step
*   tau_res      - residence time
*   tau_mix      - mixture time
*   tau_pair     - pairwise time
*
*   Output:
*   success or error
*
*   last update: Oct 1, 2019
* -----------------------------------------------------------------
*/

int pmsr_input(unsigned long int *seed,unsigned int *Np,unsigned int *Ndt,
	       double *t0,double *delta_t,double *tau_res,double *tau_mix,
	       double *tau_pair,double *Tmax,double *Tmin,
               double *atol,double *rtol)
{
    printf("\n Input PMSR parameters:\n");

    printf("\n RNG seed:");
    scanf("%lu", seed);
    printf("\n %lu\n", *seed);
    if( *seed <= 0 )
	*seed = time(NULL);

    printf("\n # of particles:");
    scanf("%d", Np);
    printf("\n %d\n", *Np);
    if( *Np <= 0 || *Np % 2 != 0 )
	GSL_ERROR(" Np must be a positive even integer",GSL_EINVAL);

    printf("\n # of time steps:");
    scanf("%d", Ndt);
    printf("\n %d\n", *Ndt);
    if( *Ndt <= 0 )
	GSL_ERROR(" Ndt must be a positive integer",GSL_EINVAL);

    printf("\n initial time:");
    scanf("%lf", t0);
    printf("\n %+.1e\n", *t0);
    if( *t0 < 0.0 )
	GSL_ERROR(" t0 must be a grather than zero",GSL_EINVAL);

    printf("\n time step:");
    scanf("%lf", delta_t);
    printf("\n %+.1e\n", *delta_t);
    if( *delta_t <= 0.0 )
	GSL_ERROR(" delta_t must be a grather than zero",GSL_EINVAL);

    printf("\n residence time:");
    scanf("%lf", tau_res);
    printf("\n %+.1e\n", *tau_res);
    if( *tau_res <= 0.0 )
	GSL_ERROR(" tau_res must be a grather than zero",GSL_EINVAL);

    printf("\n mixture time:");
    scanf("%lf", tau_mix);
    printf("\n %+.1e\n", *tau_mix);
    if( *tau_mix <= 0.0 )
	GSL_ERROR(" tau_mix must be a grather than zero",GSL_EINVAL);

    printf("\n pairwise time:");
    scanf("%lf", tau_pair);
    printf("\n %+.1e\n", *tau_pair);
    if( *tau_pair <= 0.0 )
	GSL_ERROR(" tau_pair must be a grather than zero",GSL_EINVAL);

    printf("\n temperature upper bound:");
    scanf("%lf", Tmax);
    printf("\n %+.1e\n", *Tmax);
    if( *Tmax <= 0.0 )
	GSL_ERROR(" Tmax must be a grather than zero",GSL_EINVAL);

    printf("\n temperature lower bound:");
    scanf("%lf", Tmin);
    printf("\n %+.1e\n", *Tmin);
    if( *Tmin < 0.0 || *Tmin > *Tmax )
	GSL_ERROR(" Tmin must be less than Tmax and a grather than zero",GSL_EINVAL);

    printf("\n absolute tolerance:");
    scanf("%lf", atol);
    printf("\n %+.1e\n", *atol);
    if( *atol <= 0.0 )
	GSL_ERROR(" atol must be a grather than zero",GSL_EINVAL);

    printf("\n relative tolerance:");
    scanf("%lf", rtol);
    printf("\n %+.1e\n", *rtol);
    if( *rtol <= 0.0 )
	GSL_ERROR(" rtol must be a grather than zero",GSL_EINVAL);

    return GSL_SUCCESS;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_alloc
*
*  This function allocates an internal memory block for
*  pmsr workspace struct. If successful, returns a pointer to
*  pmsr_wrk. If a startup error occurs returns NULL.
*
*   last update: Nov 5, 2009
* -----------------------------------------------------------------
*/

pmsr_wrk *pmsr_alloc()
{
    pmsr_wrk *pmsr = NULL;

    /* memory allocation for pmsr struct */
    pmsr = (pmsr_wrk *) malloc(sizeof(pmsr_wrk));
    if ( pmsr == NULL )
        return NULL;

    /* setting pmsr_wrk elements */
    pmsr->pmsr_idx = NULL;
    pmsr->pmsr_ps  = NULL;

    return pmsr;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_init
*
*  This function initiates the system of particles
*  with an initial composition phi.
*
*   Input:
*   Np   - # of particles
*   Neq  - # of equations
*   pmsr - pointer to a struct pmsr
*
*   Output:
*   success or error
*
*  last update: Sep 22, 2009
* -----------------------------------------------------------------
*/

int pmsr_init(int Np,int Neq,pmsr_wrk *pmsr)
{
    int i;

    /* checking if pmsr is NULL */
    if ( pmsr == NULL )
        return GSL_EINVAL;

    /* memory allocation for the system of particles */
    pmsr->pmsr_ps = (gsl_vector **) malloc(Np*sizeof(gsl_vector *));
    if ( pmsr->pmsr_ps == NULL )
	return GSL_ENOMEM;

    for ( i = 0; i < Np; i++ )
        pmsr->pmsr_ps[i] = gsl_vector_calloc(Neq);

    /* memory allocation for the vector of indices */
    pmsr->pmsr_idx = (int *) malloc(Np*sizeof(int));
    if ( pmsr->pmsr_idx == NULL )
    {
	for ( i = 0; i < Np; i++ )
	{
	    gsl_vector_free(pmsr->pmsr_ps[i]);
	    pmsr->pmsr_ps[i] = NULL;
	}

        free(pmsr->pmsr_ps);
	pmsr->pmsr_ps = NULL;

	return GSL_ENOMEM;
    }

    /* initial configuration of particles pairwise */
    for ( i = 0; i < Np; i++ )
        pmsr->pmsr_idx[i] = i;

    return GSL_SUCCESS;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_free
*
*   This routine frees the memory allocated by pmsr_alloc.
*
*   Input:
*   pmsr - pointer to a struct pmsr
*
*   Output:
*   void
*
*   last update: May 10, 2009
* -----------------------------------------------------------------
*/

void pmsr_free(int Np,void **pmsr_bl)
{
    int i;
    pmsr_wrk *pmsr = NULL;

    /* checking if pmsr_bl is NULL */
    if ( *pmsr_bl == NULL )
        return;

    /* checking if Np is positive */
    if ( Np <= 0 )
        return;

    pmsr = (pmsr_wrk *) (*pmsr_bl);

    /* releasing pmsr elements */
    if ( pmsr->pmsr_ps != NULL )
    {
	for ( i = 0; i < Np; i++ )
	    if ( pmsr->pmsr_ps[i] != NULL )
	    {
	        gsl_vector_free(pmsr->pmsr_ps[i]);
	        pmsr->pmsr_ps[i] = NULL;
	    }
	/* releasing pmsr struct */
	free(pmsr->pmsr_ps);
	pmsr->pmsr_ps = NULL;
    }

    if ( pmsr->pmsr_idx != NULL )
    {
	free(pmsr->pmsr_idx);
	pmsr->pmsr_idx = NULL;
    }

    free(*pmsr_bl);
    *pmsr_bl = NULL;

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_set_all
*
*  This function sets the system of particles with a given
*  composition phi.
*
*   Input:
*   pmsr - pointer to a struct pmsr
*   phi  - composition
*
*   Output:
*   success or error
*
*  last update: Mar 2, 2010
* -----------------------------------------------------------------
*/

void pmsr_set_all(int Np,gsl_vector *phi,pmsr_wrk *pmsr)
{
    int i;

    /* setting all particles with an initial phi */
    for ( i = 0; i < Np; i++ )
        gsl_vector_memcpy(pmsr->pmsr_ps[i],phi);

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_Nexc
*
*  This function computes the number of particles to be exchanged,
*  Nexc, corresponding to outﬂow/inﬂow particles.
*
*  Nexc  = ceil( 1/2 Np delta_t/taus_res )
*
*   Input:
*   Np      - # of particles
*   delta_t - time step
*   tau_res - residence time
*
*   Output:
*   Nexc    - # of particles to be exchanged
*
*   last update: Feb 12, 2009
* -----------------------------------------------------------------
*/

int pmsr_Nexc(int Np,double delta_t,double tau_res)
{
    return ceil( (Np/2)*(delta_t/tau_res) );
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*  pmsr_Npair
*
*  This function computes the number of particles to be pairwised,
*  Npair, corresponding to pairing particles.
*
*  Npair = ceil( 1/2 Np delta_t/taus_pair )
*
*   Input:
*   Np       - # of particles
*   delta_t  - time step
*   tau_pair - pairwise time
*
*   Output:
*   Npair    - # of particles to be pairwised
*
*   last update: Feb 12, 2009
* -----------------------------------------------------------------
*/

int pmsr_Npair(int Np,double delta_t,double tau_pair)
{
    return ceil( (Np/2)*(delta_t/tau_pair) );
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_meanvar
*
*   This function computes the mean and the variance
*   of a particular property in a system of particles
*   using the algorithm of Knuth/Welford.
*
*   Ref.:
*   D. E. Knuth (1998)
*   The Art of Computer Programming,
*   volume 2: Seminumerical Algorithms
*   3rd ed., Addison-Wesley.
*
*   B. P. Welford (1962).
*   "Note on a method for calculating corrected sums
*   of squares and products". Technometrics 4(3):419–420
*
*   Input:
*   Np - # of particles
*   k  - property index
*   ps - system of particle
*
*   Output:
*   mean - propertie mean value
*   var  - propertie variance
*
*   last update: Mar 4, 2010
* -----------------------------------------------------------------
*/

void pmsr_meanvar(int Np,int k,gsl_vector **ps,double *mean,double *var)
{
    int i;
    double M     = 0.0;
    double S     = 0.0;
    double delta = 0.0;

    for ( i = 0; i < Np; i++ )
    {
        delta = ps[i]->data[k] - M;
	M    += delta/(double) (i+1);
        S    += delta*(ps[i]->data[k] - M);
    }

    /* mean */
    *mean  = M;

    /* variance */
    *var   = S/(double) (i-1);

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_mixture
*
*   This function computes the mixture step in PMSR model.
*
*   Input:
*   Np      - # of particles
*   Neq     - # of equations
*   delta_t - time step
*   tau_mix - mixture time
*   idx     - particles index vector
*
*   Output:
*   ps      - system of particles
*
*   last update: Mar 9, 2009
* -----------------------------------------------------------------
*/

void pmsr_mixture(int Np,int Neq,double delta_t,double tau_mix,
					    int *idx,gsl_vector **ps)
{
    int k, i;
    double sum, dif;

    /* particle index */
    k = 0;

    /* mixture of the Np particles */
    while( k < Np/2 )
    {
        /* mixture of two particles */
        for ( i = 0; i < Neq; i++ )
        {
            /* sum = 1/2(phi_p0 + phi_q0) */
            sum = 0.5*( ps[idx[2*k]]->data[i] + ps[idx[2*k+1]]->data[i] );

            /* dif = 1/2(phi_p0 - phi_q0) */
            dif = 0.5*( ps[idx[2*k]]->data[i] - ps[idx[2*k+1]]->data[i] );

            /* phi_p =  dif.exp(-2/tau delta_t) + sum */
            ps[idx[2*k]]->data[i] =
                        dif*exp(-(2.0/tau_mix)*delta_t) + sum;

            /* phi_q = -dif.exp(-2/tau delta_t) + sum */
            ps[idx[2*k+1]]->data[i] =
                        -dif*exp(-(2.0/tau_mix)*delta_t) + sum;
        }
        k++;
    }

    return;
}
/*----------------------------------------------------------------*/

/*
* -----------------------------------------------------------------
*   pmsr_iops
*
*   This function executes the inflow/outflow,
*   pairwise and shuflle steps.
*
*   Input:
*   Np      - # of particles
*   Nexc    - # of particles to be exchanged
*   Npair   - # of particles to be pairwised
*   rand    - rng workspace
*   phi     - inflow composition
*
*   Output:
*   p       - system of particles
*   pairs   - pairs of particles index
*
*   last update: Mar 1, 2010
* -----------------------------------------------------------------
*/

void pmsr_iops(int Np,int Nexc,int Npair,gsl_rng *rand,
				    gsl_vector *phi,pmsr_wrk *pmsr)
{
    int i;
    int idx1[Nexc+Npair];
    int idx2[2*(Nexc+Npair)];
    int *aux = NULL;

    /* memory allocation */
    aux = (int *) malloc((Np/2)*sizeof(int));

    /* setting aux vector with integers from 0 to Np/2-1 */
    for ( i = 0; i < Np/2; i++ )
        aux[i] = i;

    /* selecting Nexc + Npair pairs at random */
    gsl_ran_choose(rand,idx1,Nexc+Npair,aux,Np/2,sizeof(int));
    for ( i = 0; i < Nexc + Npair; i++ )
    {
        idx2[2*i  ] = pmsr->pmsr_idx[2*idx1[i]  ];
        idx2[2*i+1] = pmsr->pmsr_idx[2*idx1[i]+1];
    }

    /* changing composition of Nexc pairs (inflow / outflow) */
    for ( i = 0; i < Nexc; i++ )
    {
        gsl_vector_memcpy(pmsr->pmsr_ps[idx2[2*i]  ],phi);
        gsl_vector_memcpy(pmsr->pmsr_ps[idx2[2*i+1]],phi);
    }

    /* shuffling the selected pairs */
    gsl_ran_shuffle(rand,idx2,2*(Nexc+Npair),sizeof(int));

    /* setting the new partners in the system */
    for ( i = 0; i < Nexc + Npair; i++ )
    {
        pmsr->pmsr_idx[2*idx1[i]  ] = idx2[2*i  ];
        pmsr->pmsr_idx[2*idx1[i]+1] = idx2[2*i+1];
    }

    /* releasing allocated memory */
    free(aux);
    aux = NULL;

    return;
}
/*----------------------------------------------------------------*/


#include <stdio.h>
#include <math.h>
#include <gsl/gsl_sf_bessel.h>
#include "utility.h"
#include "constants.h"
#include <gsl/gsl_cblas.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <omp.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_sf_pow_int.h>

#ifdef EXTERN_VARIABLES
#define EXTERN
#else
#define EXTERN extern
#endif

#define OSSZI 14000
#define	SIMULATIONEN 10
#define KUPF_BINARY	1				// set to 1 for Kupferman/ fully mobile bath
#define VIRTUAL_FLAG	0			// set to 1 to transfer force one way onto virtual particle, no bath recouoling
#define SIMPSON_BINARY 1
#define REFLECTALL_FLAG 0
#define OMMEGA_READ_BINARY 0		// if true, don't regenerate ommega. use ommega.dat
#define DIM 1
#define ORDER ((2 * OSSZI + 2) * DIM)
#define LABELPREFIX "GIT_HUB"	//a string to add to your result folder
#define T_POWERS 0 							// no function, just compiler needs to know
#define T_MULT 1.0              			// same
#define LOGSCALE_BINARY 0       			// leave at 0, toggles time setup. WIP
#define TIME_STEPS  0.05					// snapshot intervall for saved values
#define LENGTH_T 	1000  +  9 				// Number N of saved steps (format must be N +9)
#define TIME_SHIFT 500
#define TIME_END (double)((double) (LENGTH_T-9) * TIME_STEPS) //leave macro as is
//#endif
#define THREADSNR 1 						// most efficient at dimension
#define	KBOLTZ  1.380641e-8
#define	TEMP  	100.0
#define	GAMMA  1.0 							// drag
#define LOWER_CUTOFF_FLAG 0 				// 1 for lower cutoff frequency when settinh up the ommega
#define	ALPHA  0.8							// exponent free random walk msqdplcmnt t^alpha
#define	mass  1.0 							// mass tracer particle
#define	TIME_ARRIVAL 3000.0 				// Diffusion time over lattice cell
#define MAXSTEPSIZE_BINARY	0 				// set to 1 to decouple from smallest period to fixed N * DT = MAXSTEPSIEMULT
#define MAXSTEPSIZEMULT    5.0e-4			// stepsize in terms of shortest period scale
#define	NUMBER_TO_INF  (500)				// muss gerade sein, hindernisse die Symetrisch um null verteilt sind in 1-D
#define	VOL_FRAC  0.0
#define LABEL "ZWANZIG_THERMALIZED"			// "ZWANZIG" or "ZWANZIG_THERMALISED"
#define SUMMATION_METHOD Neumaier_Sum  		// Sign_, Kahan oder Neumaier_ gefolgt von sum
#define STEPPER_METHOD VVerlet_Step_deriv_Box	//bis jetzt VVerlet_Step_Target_Square oder VVerlet_Step_deriv oder VVerlet_Step_Yukawa Verlet_Step_Target_Pore_Rectangle
#define POTI_HANDLE deriv_parallel
#define NORM_INT 0  						// für Norm_Diff die art der p-norm, 0 für infty
#define MAX_NR_PLOTS 50
#define ESCAPE_CELLS  0                		// anzahl der gitterzellen ohne hinderniss -1
#define REFLEC_BINARY 1 					// toggle bewteewn reflecting new(1) or old(0) velocity, or no velcotiy adjust (2)
#define FLAG_RAND_BATH 0 					// set 1 for unifrom random bath, 0 for uniform deteriministic

#define DENSITY_SAMPLING_LENGTH 10
#define DENSITY_SAMPLING_POINTS 10
#define DIFF_COEFF_MODIFY 0.01 				// modify for VVerlet_Step_drag_sphere
#define DENSITY_SNAPSHOTS 5
#define TARGET_METHOD	Get_Lattice_Targets_Corner_Square // method to determine free cells
#define PARALLEL  //nicht definieren falls ohne -fopenmp compiliert!
#ifdef PARALLEL
	#define THREADS THREADSNR
	#define SETNUMTHREADS omp_set_num_threads(THREADSNR);
#else
	#define SETNUMTHREADS
	#define THREADS 1  //wichtig zur zeitberechnung dass dann THREADS auf 1 gesetzt wird, wenn nicht parallel operiert
#endif

//#define OCL
#ifdef OCL
	#define VVERLET Opencl_VVerlet
#else
	#define VVERLET VVerlet_parallel
#endif
EXTERN int FLAG_DUMP_VALUE;			// if error occurs set 1, dump calculation, start fresh
EXTERN int NUMBER_PORE_OBSTACLES;  	//number of spheres in Yukawa pore wall per side
EXTERN double LATTICE_SPACING;
EXTERN double DIFF_COEFF;
EXTERN double TARGET_LENGTH;
EXTERN double VIRTUAL_X[DIM];
EXTERN double VIRTUAL_V[DIM];
EXTERN int OUTPUT_FLAG;				//set 1 before first output, after that 0 to hide messages
									// from Setuo functions for ommega and gamma
EXTERN int TARGET_CONTACT;			// counts number of hits on lattice
EXTERN int TARGET_FLAG;				// Toggles after first hit per run
EXTERN int lattice_position[DIM];	// aktuelle Gitterzelle
EXTERN double massq[OSSZI];
EXTERN double time_first_contact;
EXTERN double coupling[OSSZI];  //Koppl. Konst. Gamma im Zwanzig
EXTERN double ommega[OSSZI]; 	// Osszi Kreisfreq
EXTERN double y[ORDER];			// Startvec
EXTERN double MAXSTEPSIZE;		//Schrittweite Integrator
EXTERN double LASTSTEPSIZE;
EXTERN int **POSITIONS; 	//enthält Koordinated der Gitterpunkte
EXTERN double L_BALL;		// ballistische Länge der Diffusion
EXTERN int (*Sum_Method)(int N, double input[], double *ans);
EXTERN void (*Poti_Handle)(const double *y, double *ans, double t,int N); 			// externe oder Hindernisskraft
EXTERN void (*Stepper_Method)(int N,  double  x[N/2] , double v[N/2], double a[N/2], double *t,
					void (*deriv) (double *y, double *ans, double t,int N));
					// Methode mit oder ohne Hindernisse für Verlet_schritt
EXTERN gsl_rng * RAND_GLOBAL;	// globally accessible random variable


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

#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <gsl/gsl_permutation.h>
#include <gsl/gsl_permute_double.h>
#include <gsl/gsl_test.h>
#include <gsl/gsl_ieee_utils.h>

unsigned int p5[120][5] = {
  {0, 1, 2, 3, 4}, {0, 1, 2, 4, 3}, {0, 1, 3, 2, 4}, {0, 1, 3, 4, 2},
  {0, 1, 4, 2, 3}, {0, 1, 4, 3, 2}, {0, 2, 1, 3, 4}, {0, 2, 1, 4, 3},
  {0, 2, 3, 1, 4}, {0, 2, 3, 4, 1}, {0, 2, 4, 1, 3}, {0, 2, 4, 3, 1},
  {0, 3, 1, 2, 4}, {0, 3, 1, 4, 2}, {0, 3, 2, 1, 4}, {0, 3, 2, 4, 1},
  {0, 3, 4, 1, 2}, {0, 3, 4, 2, 1}, {0, 4, 1, 2, 3}, {0, 4, 1, 3, 2},
  {0, 4, 2, 1, 3}, {0, 4, 2, 3, 1}, {0, 4, 3, 1, 2}, {0, 4, 3, 2, 1},
  {1, 0, 2, 3, 4}, {1, 0, 2, 4, 3}, {1, 0, 3, 2, 4}, {1, 0, 3, 4, 2},
  {1, 0, 4, 2, 3}, {1, 0, 4, 3, 2}, {1, 2, 0, 3, 4}, {1, 2, 0, 4, 3},
  {1, 2, 3, 0, 4}, {1, 2, 3, 4, 0}, {1, 2, 4, 0, 3}, {1, 2, 4, 3, 0},
  {1, 3, 0, 2, 4}, {1, 3, 0, 4, 2}, {1, 3, 2, 0, 4}, {1, 3, 2, 4, 0},
  {1, 3, 4, 0, 2}, {1, 3, 4, 2, 0}, {1, 4, 0, 2, 3}, {1, 4, 0, 3, 2},
  {1, 4, 2, 0, 3}, {1, 4, 2, 3, 0}, {1, 4, 3, 0, 2}, {1, 4, 3, 2, 0},
  {2, 0, 1, 3, 4}, {2, 0, 1, 4, 3}, {2, 0, 3, 1, 4}, {2, 0, 3, 4, 1},
  {2, 0, 4, 1, 3}, {2, 0, 4, 3, 1}, {2, 1, 0, 3, 4}, {2, 1, 0, 4, 3},
  {2, 1, 3, 0, 4}, {2, 1, 3, 4, 0}, {2, 1, 4, 0, 3}, {2, 1, 4, 3, 0},
  {2, 3, 0, 1, 4}, {2, 3, 0, 4, 1}, {2, 3, 1, 0, 4}, {2, 3, 1, 4, 0},
  {2, 3, 4, 0, 1}, {2, 3, 4, 1, 0}, {2, 4, 0, 1, 3}, {2, 4, 0, 3, 1},
  {2, 4, 1, 0, 3}, {2, 4, 1, 3, 0}, {2, 4, 3, 0, 1}, {2, 4, 3, 1, 0},
  {3, 0, 1, 2, 4}, {3, 0, 1, 4, 2}, {3, 0, 2, 1, 4}, {3, 0, 2, 4, 1},
  {3, 0, 4, 1, 2}, {3, 0, 4, 2, 1}, {3, 1, 0, 2, 4}, {3, 1, 0, 4, 2},
  {3, 1, 2, 0, 4}, {3, 1, 2, 4, 0}, {3, 1, 4, 0, 2}, {3, 1, 4, 2, 0},
  {3, 2, 0, 1, 4}, {3, 2, 0, 4, 1}, {3, 2, 1, 0, 4}, {3, 2, 1, 4, 0},
  {3, 2, 4, 0, 1}, {3, 2, 4, 1, 0}, {3, 4, 0, 1, 2}, {3, 4, 0, 2, 1},
  {3, 4, 1, 0, 2}, {3, 4, 1, 2, 0}, {3, 4, 2, 0, 1}, {3, 4, 2, 1, 0},
  {4, 0, 1, 2, 3}, {4, 0, 1, 3, 2}, {4, 0, 2, 1, 3}, {4, 0, 2, 3, 1},
  {4, 0, 3, 1, 2}, {4, 0, 3, 2, 1}, {4, 1, 0, 2, 3}, {4, 1, 0, 3, 2},
  {4, 1, 2, 0, 3}, {4, 1, 2, 3, 0}, {4, 1, 3, 0, 2}, {4, 1, 3, 2, 0},
  {4, 2, 0, 1, 3}, {4, 2, 0, 3, 1}, {4, 2, 1, 0, 3}, {4, 2, 1, 3, 0},
  {4, 2, 3, 0, 1}, {4, 2, 3, 1, 0}, {4, 3, 0, 1, 2}, {4, 3, 0, 2, 1},
  {4, 3, 1, 0, 2}, {4, 3, 1, 2, 0}, {4, 3, 2, 0, 1}, {4, 3, 2, 1, 0}
} ;

unsigned int c5[120][5] = {
  {4, 3, 2, 1, 0}, {3, 4, 2, 1, 0}, {4, 2, 3, 1, 0}, {2, 3, 4, 1, 0},
  {2, 4, 3, 1, 0}, {3, 2, 4, 1, 0}, {4, 3, 1, 2, 0}, {3, 4, 1, 2, 0},
  {4, 1, 2, 3, 0}, {1, 2, 3, 4, 0}, {1, 2, 4, 3, 0}, {3, 1, 2, 4, 0},
  {4, 1, 3, 2, 0}, {1, 3, 4, 2, 0}, {4, 2, 1, 3, 0}, {2, 1, 3, 4, 0},
  {2, 4, 1, 3, 0}, {1, 3, 2, 4, 0}, {1, 4, 3, 2, 0}, {3, 1, 4, 2, 0},
  {2, 1, 4, 3, 0}, {3, 2, 1, 4, 0}, {1, 4, 2, 3, 0}, {2, 3, 1, 4, 0},
  {4, 3, 2, 0, 1}, {3, 4, 2, 0, 1}, {4, 2, 3, 0, 1}, {2, 3, 4, 0, 1},
  {2, 4, 3, 0, 1}, {3, 2, 4, 0, 1}, {4, 3, 0, 1, 2}, {3, 4, 0, 1, 2},
  {4, 0, 1, 2, 3}, {0, 1, 2, 3, 4}, {0, 1, 2, 4, 3}, {3, 0, 1, 2, 4},
  {4, 0, 1, 3, 2}, {0, 1, 3, 4, 2}, {4, 2, 0, 1, 3}, {2, 0, 1, 3, 4},
  {2, 4, 0, 1, 3}, {0, 1, 3, 2, 4}, {0, 1, 4, 3, 2}, {3, 0, 1, 4, 2},
  {2, 0, 1, 4, 3}, {3, 2, 0, 1, 4}, {0, 1, 4, 2, 3}, {2, 3, 0, 1, 4},
  {4, 3, 0, 2, 1}, {3, 4, 0, 2, 1}, {4, 0, 2, 3, 1}, {0, 2, 3, 4, 1},
  {0, 2, 4, 3, 1}, {3, 0, 2, 4, 1}, {4, 3, 1, 0, 2}, {3, 4, 1, 0, 2},
  {4, 1, 0, 2, 3}, {1, 0, 2, 3, 4}, {1, 0, 2, 4, 3}, {3, 1, 0, 2, 4},
  {4, 1, 3, 0, 2}, {1, 3, 4, 0, 2}, {4, 0, 2, 1, 3}, {0, 2, 1, 3, 4},
  {0, 2, 4, 1, 3}, {1, 3, 0, 2, 4}, {1, 4, 3, 0, 2}, {3, 1, 4, 0, 2},
  {0, 2, 1, 4, 3}, {3, 0, 2, 1, 4}, {1, 4, 0, 2, 3}, {0, 2, 3, 1, 4},
  {4, 0, 3, 2, 1}, {0, 3, 4, 2, 1}, {4, 2, 0, 3, 1}, {2, 0, 3, 4, 1},
  {2, 4, 0, 3, 1}, {0, 3, 2, 4, 1}, {4, 1, 0, 3, 2}, {1, 0, 3, 4, 2},
  {4, 2, 1, 0, 3}, {2, 1, 0, 3, 4}, {2, 4, 1, 0, 3}, {1, 0, 3, 2, 4},
  {4, 0, 3, 1, 2}, {0, 3, 4, 1, 2}, {4, 1, 2, 0, 3}, {1, 2, 0, 3, 4},
  {1, 2, 4, 0, 3}, {0, 3, 1, 2, 4}, {0, 3, 1, 4, 2}, {1, 4, 0, 3, 2},
  {1, 4, 2, 0, 3}, {0, 3, 2, 1, 4}, {2, 1, 4, 0, 3}, {2, 0, 3, 1, 4},
  {0, 4, 3, 2, 1}, {3, 0, 4, 2, 1}, {2, 0, 4, 3, 1}, {3, 2, 0, 4, 1},
  {0, 4, 2, 3, 1}, {2, 3, 0, 4, 1}, {1, 0, 4, 3, 2}, {3, 1, 0, 4, 2},
  {2, 1, 0, 4, 3}, {3, 2, 1, 0, 4}, {1, 0, 4, 2, 3}, {2, 3, 1, 0, 4},
  {0, 4, 3, 1, 2}, {3, 0, 4, 1, 2}, {1, 2, 0, 4, 3}, {3, 1, 2, 0, 4},
  {0, 4, 1, 2, 3}, {1, 2, 3, 0, 4}, {1, 3, 0, 4, 2}, {0, 4, 1, 3, 2},
  {0, 4, 2, 1, 3}, {1, 3, 2, 0, 4}, {2, 0, 4, 1, 3}, {2, 1, 3, 0, 4}
} ;

unsigned int cycles[120] = {
  5, 4, 4, 3, 3, 4, 4, 3, 3, 2,
  2, 3, 3, 2, 4, 3, 3, 2, 2, 3,
  3, 4, 2, 3, 4, 3, 3, 2, 2, 3,
  3, 2, 2, 1, 1, 2, 2, 1, 3, 2,
  2, 1, 1, 2, 2, 3, 1, 2, 3, 2,
  2, 1, 1, 2, 4, 3, 3, 2, 2, 3,
  3, 2, 2, 1, 1, 2, 2, 3, 1, 2,
  2, 1, 2, 1, 3, 2, 2, 1, 3, 2,
  4, 3, 3, 2, 2, 1, 3, 2, 2, 1,
  1, 2, 2, 1, 3, 2, 1, 2, 2, 3,
  1, 2, 2, 3, 3, 4, 2, 3, 1, 2,
  2, 3, 1, 2, 2, 1, 1, 2, 2, 3
} ;

unsigned int inversions[120] = {
  0, 1, 1, 2, 2, 3, 1, 2, 2, 3,
  3, 4, 2, 3, 3, 4, 4, 5, 3, 4,
  4, 5, 5, 6, 1, 2, 2, 3, 3, 4,
  2, 3, 3, 4, 4, 5, 3, 4, 4, 5,
  5, 6, 4, 5, 5, 6, 6, 7, 2, 3,
  3, 4, 4, 5, 3, 4, 4, 5, 5, 6,
  4, 5, 5, 6, 6, 7, 5, 6, 6, 7,
  7, 8, 3, 4, 4, 5, 5, 6, 4, 5,
  5, 6, 6, 7, 5, 6, 6, 7, 7, 8,
  6, 7, 7, 8, 8, 9, 4, 5, 5, 6,
  6, 7, 5, 6, 6, 7, 7, 8, 6, 7,
  7, 8, 8, 9, 7, 8, 8, 9, 9, 10
} ;

int
main (void)
{
  gsl_ieee_env_setup ();

  {
    int i = 0, j, status = 0;

    gsl_permutation * p ;

    p = gsl_permutation_alloc (5);

    gsl_permutation_init (p);

    do
      {
        for (j = 0; j < 5; j++)
          {
            status |= (p->data[j] != p5[i][j]);
          }

        i++;
      }
    while (gsl_permutation_next(p) == GSL_SUCCESS);

    gsl_test(status, "gsl_permutation_next, 5-th order permutation, 120 steps");

    do
      {
        i--;

        for (j = 0; j < 5; j++)
          {
            status |= (p->data[j] != p5[i][j]);
          }
      }
    while (gsl_permutation_prev(p) == GSL_SUCCESS);

    gsl_test(status, "gsl_permutation_prev, 5-th order permutation, 120 steps");

    gsl_permutation_free (p);
  }

#ifdef JUNK
  {
    int i;
    int status = 0 ;

    gsl_permutation * p1 = gsl_permutation_alloc (5);
    gsl_permutation * p2 = gsl_permutation_alloc (5);
    gsl_permutation * p = gsl_permutation_alloc (5);

    double v[5] = { 100.0, 101.0, 102.0, 103.0, 104.0 } ;

    gsl_permutation_init (p1);

    do
      {
        gsl_permutation_init (p2);

        do
          {
            double x[5], y[5];

            /* Compute x= p1 p2 v */
            memcpy (x, v, 5 * sizeof(double));
            gsl_permute (p2->data, x, 1, 5);
            gsl_permute (p1->data, x, 1, 5);

            /* Compute P= p1 p2, y = P v */
            gsl_permutation_mul (p, p1, p2);
            memcpy (y, v, 5 * sizeof(double));
            gsl_permute (p->data, y, 1, 5);

            for (i = 0; i < 5; i++)
              {
                if (x[i] != y[i])
                  status = 1;
              }

            if (status == 1)
              break;
          }
        while (gsl_permutation_next(p2) == GSL_SUCCESS);

        if (status == 1)
          break;
      }
    while (gsl_permutation_next(p1) == GSL_SUCCESS);

    gsl_permutation_free (p1);
    gsl_permutation_free (p2);
    gsl_permutation_free (p);

    gsl_test(status, "gsl_permutation_mul, all 5-th order combinations");
  }
#endif

  /* testing cycles representations */
  {
    int i = 0, j, status = 0;

    gsl_permutation * p = gsl_permutation_alloc (5);

    gsl_permutation * plin = gsl_permutation_alloc (5);
    gsl_permutation * pcan = gsl_permutation_alloc (5);

    gsl_permutation_init (p);

    do
      {
        gsl_permutation_memcpy (plin, p);

        for (j = 0; j < 5; j++)
          {
            pcan->data[j] = 0;
          }

        gsl_permutation_linear_to_canonical (pcan, plin);

        for (j = 0; j < 5; j++)
          {
            status |= (pcan->data[j] != c5[i][j]);
          }

        status |= (gsl_permutation_canonical_cycles (pcan) != cycles[i]);

        status |= (gsl_permutation_linear_cycles (plin) != cycles[i]);

        for (j = 0; j < 5; j++)
          {
            plin->data[j] = 0;
          }

        gsl_permutation_canonical_to_linear (plin, pcan);

        for (j = 0; j < 5; j++)
          {
            status |= (plin->data[j] != p5[i][j]);
          }

        i++;
      }
    while (gsl_permutation_next(p) == GSL_SUCCESS);

    gsl_permutation_free (p);
    gsl_permutation_free (plin);
    gsl_permutation_free (pcan);

    gsl_test (status, "gsl_permutation canonical conversion, 5-th order permutation, 120 steps");
  }

  /* testing number of inversions */
  {
    int i = 0, status = 0;

    gsl_permutation * p = gsl_permutation_alloc (5);

    gsl_permutation_init (p);

    do
      {
        status |= gsl_permutation_inversions (p) != inversions[i];
        i++;
      }
    while (gsl_permutation_next(p) == GSL_SUCCESS);

    gsl_permutation_free (p);

    gsl_test (status, "gsl_permutation_inversions, 5-th order permutation, 120 steps");
  }

  exit (gsl_test_summary());
}


#pragma once

#include "SimpleEvaluator.h"
#include "BooleanDAG.h"
#include "Interface.h"
#include "SuggestionGenerator.h"
#include <map>
#include <set>

#ifndef _NOGSL
#include <gsl/gsl_vector.h>
#else
#include "FakeGSL.h"
#endif

class SimpleSuggestionGenerator: public SuggestionGenerator {
    vector<int> nodesToSuggest;
    Interface* interf;
    BooleanDAG* dag;
    map<string, int>& ctrls;
    SimpleEvaluator* seval;
public:
    SimpleSuggestionGenerator(BooleanDAG* _dag, Interface* _interface, map<string, int>& _ctrls): dag(_dag), interf(_interface), ctrls(_ctrls) {
        seval = new SimpleEvaluator(*dag, ctrls);
        seval->setInputs(interf);
        for (auto it = interf->varsMapping.begin(); it != interf->varsMapping.end(); it++) {
            int nodeid = it->first;
            bool_node* n = (*dag)[nodeid];
            if (n->type == bool_node::LT) {
                nodesToSuggest.push_back(nodeid);
            }
        }
    }

    pair<int, int> getSuggestion(const gsl_vector* state) {
        return make_pair(-1, 0);
    }
    pair<int, int> getUnsatSuggestion(const gsl_vector* state) {
        return make_pair(-1, 0);
    }
    virtual IClause* getConflictClause(int level, LocalState * state) {
        return NULL;
    }

    virtual SClause* getUnsatClause(const gsl_vector* state) {
        return NULL;
    }

    virtual SClause* getUnsatClause(const gsl_vector* state, const gsl_vector* prevState) {
        return NULL;
    }
};


/**
 * @file zheevd.c
 *
 *  PLASMA computational routines
 *  Release Date: November, 15th 2009
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Azzam Haidar
 * @author Hatem Ltaief
 * @date 2010-11-15
 * @precisions normal z -> s d c
 *
 **/
#include <lapacke.h>
#include "common.h"
/***************************************************************************//**
 *
 * @ingroup PLASMA_Complex64_t
 *
 *  PLASMA_zheevd - Computes all eigenvalues and, optionally,
 *  eigenvectors of a complex Hermitian matrix A. The matrix A is
 *  preliminary reduced to tridiagonal form using a two-stage
 *  approach:
 *  First stage: reduction to band tridiagonal form;
 *  Second stage: reduction from band to tridiagonal form.
 *
 *******************************************************************************
 *
 * @param[in] jobz
 *          Intended usage:
 *          = PlasmaNoVec: computes eigenvalues only;
 *          = PlasmaVec: computes eigenvalues and eigenvectors.
 *
 * @param[in] uplo
 *          Specifies whether the matrix A is upper triangular or
 *          lower triangular:
 *          = PlasmaUpper: Upper triangle of A is stored;
 *          = PlasmaLower: Lower triangle of A is stored.
 *
 * @param[in] N
 *          The order of the matrix A. N >= 0.
 *
 * @param[in,out] A
 *          On entry, the symmetric (or Hermitian) matrix A.
 *          If uplo = PlasmaUpper, the leading N-by-N upper triangular
 *          part of A contains the upper triangular part of the matrix
 *          A, and the strictly lower triangular part of A is not
 *          referenced.
 *          If uplo = PlasmaLower, the leading N-by-N lower triangular
 *          part of A contains the lower triangular part of the matrix
 *          A, and the strictly upper triangular part of A is not
 *          referenced.
 *          On exit, the lower triangle (if uplo = PlasmaLower) or the
 *          upper triangle (if uplo = PlasmaUpper) of A, including the
 *          diagonal, is destroyed.
 *
 * @param[in] LDA
 *          The leading dimension of the array A. LDA >= max(1,N).
 *
 * @param[out] W
 *          On exit, if info = 0, the eigenvalues.
 *
 * @param[in, out] descT
 *          On entry, descriptor as return by PLASMA_Alloc_Workspace_zheevd
 *          On exit, contains auxiliary factorization data.
 *
 * @param[out] Q
 *          On exit, if jobz = PlasmaVec and info = 0, the eigenvectors.
 *
 * @param[in] LDQ
 *          The leading dimension of the array Q. LDQ >= max(1,N).
 *
 *******************************************************************************
 *
 * @return
 *          \retval PLASMA_SUCCESS successful exit
 *          \retval <0 if -i, the i-th argument had an illegal value
 *          \retval >0 if INFO = i, the algorithm failed to converge; i
 *               off-diagonal elements of an intermediate tridiagonal
 *               form did not converge to zero.
 *
 *******************************************************************************
 *
 * @sa PLASMA_zheevd_Tile
 * @sa PLASMA_zheevd_Tile_Async
 * @sa PLASMA_cheevd
 * @sa PLASMA_dsyev
 * @sa PLASMA_ssyev
 *
 ******************************************************************************/
int PLASMA_zheevd(PLASMA_enum jobz, PLASMA_enum uplo, int N,
                 PLASMA_Complex64_t *A, int LDA,
                 double *W,
                 PLASMA_desc *descT,
                 PLASMA_Complex64_t *Q, int LDQ)
{
    int NB;
    int status;
    plasma_context_t *plasma;
    PLASMA_sequence *sequence = NULL;
    PLASMA_request request = PLASMA_REQUEST_INITIALIZER;
    PLASMA_desc descA;

    plasma = plasma_context_self();
    if (plasma == NULL) {
        plasma_error("PLASMA_zheevd", "PLASMA not initialized");
        return PLASMA_ERR_NOT_INITIALIZED;
    }

    /* Check input arguments */
    if (jobz != PlasmaNoVec && jobz != PlasmaVec) {
        plasma_error("PLASMA_zheevd", "illegal value of jobz");
        return -1;
    }
    if (uplo != PlasmaLower && uplo != PlasmaUpper) {
        plasma_error("PLASMA_zheevd", "illegal value of uplo");
        return -2;
    }
    if (N < 0) {
        plasma_error("PLASMA_zheevd", "illegal value of N");
        return -3;
    }
    if (LDA < max(1, N)) {
        plasma_error("PLASMA_zheevd", "illegal value of LDA");
        return -5;
    }
    if (LDQ < max(1, N)) {
        plasma_error("PLASMA_zheevd", "illegal value of LDQ");
        return -9;
    }

    /* Quick return */
    if (N == 0)
        return PLASMA_SUCCESS;

    /* Tune NB & IB depending on N; Set NBNB */
    status = plasma_tune(PLASMA_FUNC_ZHEEVD, N, N, 0);
    if (status != PLASMA_SUCCESS) {
        plasma_error("PLASMA_zheevd", "plasma_tune() failed");
        return status;
    }

    /* Set NT */
    NB = PLASMA_NB;

    plasma_sequence_create(plasma, &sequence);

    if ( PLASMA_TRANSLATION == PLASMA_OUTOFPLACE ) {
        plasma_zooplap2tile( descA, A, NB, NB, LDA, N, 0, 0, N, N, sequence, &request,
                             plasma_desc_mat_free(&(descA)) );
    } else {
        plasma_ziplap2tile( descA, A, NB, NB, LDA, N, 0, 0, N, N,
                            sequence, &request);
    }

    /* Call the tile interface */
    PLASMA_zheevd_Tile_Async(jobz, uplo, &descA, W, descT, Q, LDQ, sequence, &request);

    if ( PLASMA_TRANSLATION == PLASMA_OUTOFPLACE ) {
        plasma_zooptile2lap( descA, A, NB, NB, LDA, N,  sequence, &request);
        plasma_dynamic_sync();
        plasma_desc_mat_free(&descA);
    } else {
        plasma_ziptile2lap( descA, A, NB, NB, LDA, N,  sequence, &request);
        plasma_dynamic_sync();
    }

    status = sequence->status;
    plasma_sequence_destroy(plasma, sequence);
    return status;
}
/***************************************************************************//**
 *
 * @ingroup PLASMA_Complex64_t_Tile
 *
 *  PLASMA_zheevd_Tile - Computes all eigenvalues and, optionally, eigenvectors of a
 *  complex Hermitian matrix A using a two-stage approach:
 *  First stage: reduction to band tridiagonal form;
 *  Second stage: reduction from band to tridiagonal form.
 *
 *  Operates on matrices stored by tiles.
 *  All matrices are passed through descriptors.
 *  All dimensions are taken from the descriptors.
 *
 *******************************************************************************
 *
 * @param[in] jobz
 *          Intended usage:
 *          = PlasmaNoVec: computes eigenvalues only;
 *          = PlasmaVec: computes eigenvalues and eigenvectors.
 *
 * @param[in] uplo
 *          Specifies whether the matrix A is upper triangular or
 *          lower triangular:
 *          = PlasmaUpper: Upper triangle of A is stored;
 *          = PlasmaLower: Lower triangle of A is stored.
 *
 * @param[in,out] A
 *          On entry, the symmetric (or Hermitian) matrix A.
 *          If uplo = PlasmaUpper, the leading N-by-N upper triangular
 *          part of A contains the upper triangular part of the matrix
 *          A, and the strictly lower triangular part of A is not
 *          referenced.
 *          If UPLO = 'L', the leading N-by-N lower triangular part of
 *          A contains the lower triangular part of the matrix A, and
 *          the strictly upper triangular part of A is not referenced.
 *          On exit, if jobz = PlasmaVec, then if return value = 0, A
 *          contains the orthonormal eigenvectors of the matrix A.
 *          If jobz = PlasmaNoVec, then on exit the lower triangle (if
 *          uplo = PlasmaLower) or the upper triangle (if uplo =
 *          PlasmaUpper) of A, including the diagonal, is destroyed.*
 *
 * @param[out] W
 *          On exit, if info = 0, the eigenvalues.
 *
 * @param[in,out] T
 *          On entry, descriptor as return by
 *          PLASMA_Alloc_Workspace_zheevd
 *          On exit, contains auxiliary factorization data.
 *
 * @param[out] Q
 *          On exit, if jobz = PlasmaVec and info = 0, the eigenvectors.
 *
 * @param[in] LDQ
 *          The leading dimention of the eigenvectors matrix Q. LDQ >= max(1,N).
 *
 *******************************************************************************
 *
 * @return
 *          \retval PLASMA_SUCCESS successful exit
 *          \retval <0 if -i, the i-th argument had an illegal value
 *          \retval >0 if INFO = i, the algorithm failed to converge; i
 *               off-diagonal elements of an intermediate tridiagonal
 *               form did not converge to zero.
 *
 *******************************************************************************
 *
 * @sa PLASMA_zheevd_Tile
 * @sa PLASMA_zheevd_Tile_Async
 * @sa PLASMA_cheevd
 * @sa PLASMA_dsyev
 * @sa PLASMA_ssyev
 *
 ******************************************************************************/
int PLASMA_zheevd_Tile(PLASMA_enum jobz, PLASMA_enum uplo,
                      PLASMA_desc *A, double *W,
                      PLASMA_desc *T, PLASMA_Complex64_t *Q, int LDQ)
{
    plasma_context_t *plasma;
    PLASMA_sequence *sequence = NULL;
    PLASMA_request request = PLASMA_REQUEST_INITIALIZER;
    int status;

    plasma = plasma_context_self();
    if (plasma == NULL) {
        plasma_fatal_error("PLASMA_zheevd_Tile", "PLASMA not initialized");
        return PLASMA_ERR_NOT_INITIALIZED;
    }
    plasma_sequence_create(plasma, &sequence);
    PLASMA_zheevd_Tile_Async(jobz, uplo, A, W, T, Q, LDQ, sequence, &request);
    plasma_dynamic_sync();
    status = sequence->status;
    plasma_sequence_destroy(plasma, sequence);
    return status;
}

/***************************************************************************//**
 *
 * @ingroup PLASMA_Complex64_t_Tile_Async
 *
 *  PLASMA_zheevd_Tile_Async - Computes all eigenvalues and,
 *  optionally, eigenvectors of a complex Hermitian matrix A using a
 *  two-stage approach:
 *  First stage: reduction to band tridiagonal form;
 *  Second stage: reduction from band to tridiagonal form.
 *
 *  May return before the computation is finished.
 *  Allows for pipelining of operations at runtime.
 *
 *******************************************************************************
 *
 * @param[in] jobz
 *          Intended usage:
 *          = PlasmaNoVec: computes eigenvalues only;
 *          = PlasmaVec: computes eigenvalues and eigenvectors.
 *
 * @param[in] uplo
 *          Specifies whether the matrix A is upper triangular or
 *          lower triangular:
 *          = PlasmaUpper: Upper triangle of A is stored;
 *          = PlasmaLower: Lower triangle of A is stored.
 *
 * @param[in,out] A
 *          On entry, the symmetric (or Hermitian) matrix A.
 *          If uplo = PlasmaUpper, the leading N-by-N upper triangular
 *          part of A contains the upper triangular part of the matrix
 *          A, and the strictly lower triangular part of A is not
 *          referenced.
 *          If UPLO = 'L', the leading N-by-N lower triangular part of
 *          A contains the lower triangular part of the matrix A, and
 *          the strictly upper triangular part of A is not referenced.
 *          On exit, if jobz = PlasmaVec, then if return value = 0, A
 *          contains the orthonormal eigenvectors of the matrix A.
 *          If jobz = PlasmaNoVec, then on exit the lower triangle (if
 *          uplo = PlasmaLower) or the upper triangle (if uplo =
 *          PlasmaUpper) of A, including the diagonal, is destroyed.*
 *
 * @param[out] W
 *          On exit, if info = 0, the eigenvalues.
 *
 * @param[in,out] T
 *          On entry, descriptor as return by
 *          PLASMA_Alloc_Workspace_zheevd
 *          On exit, contains auxiliary factorization data.
 *
 * @param[out] Q
 *          On exit, if jobz = PlasmaVec and info = 0, the eigenvectors.
 *
 * @param[in] LDQ
 *          The leading dimention of the eigenvectors matrix Q. LDQ >= max(1,N).
 *
 * @param[in] sequence
 *          Identifies the sequence of function calls that this call belongs to
 *          (for completion checks and exception handling purposes).
 *
 * @param[out] request
 *          Identifies this function call (for exception handling purposes).
 *
 *******************************************************************************
 *
 * @sa PLASMA_zheevd
 * @sa PLASMA_zheevd_Tile
 * @sa PLASMA_cheevd_Tile_Async
 * @sa PLASMA_dsyev_Tile_Async
 * @sa PLASMA_ssyev_Tile_Async
 *
 ******************************************************************************/
int PLASMA_zheevd_Tile_Async(PLASMA_enum jobz, PLASMA_enum uplo,
                            PLASMA_desc *A,
                            double *W,
                            PLASMA_desc *T,
                            PLASMA_Complex64_t *Q, int LDQ,
                            PLASMA_sequence *sequence, PLASMA_request *request)
{
    plasma_context_t *plasma;
    PLASMA_desc descA;
    PLASMA_desc descT;
    PLASMA_desc descQ;
    PLASMA_Complex64_t *AB;
    double *E;
    int N;
    int NB;
    int LDAB;
    int status;

    plasma = plasma_context_self();
    if (plasma == NULL) {
        plasma_fatal_error("PLASMA_zheevd_Tile_Async", "PLASMA not initialized");
        return PLASMA_ERR_NOT_INITIALIZED;
    }
    if (sequence == NULL) {
        plasma_fatal_error("PLASMA_zheevd_Tile_Async", "NULL sequence");
        return PLASMA_ERR_UNALLOCATED;
    }
    if (request == NULL) {
        plasma_fatal_error("PLASMA_zheevd_Tile_Async", "NULL request");
        return PLASMA_ERR_UNALLOCATED;
    }
    /* Check sequence status */
    if (sequence->status == PLASMA_SUCCESS)
        request->status = PLASMA_SUCCESS;
    else
        return plasma_request_fail(sequence, request, PLASMA_ERR_SEQUENCE_FLUSHED);

    /* Check descriptors for correctness */
    if (plasma_desc_check(A) != PLASMA_SUCCESS) {
        plasma_error("PLASMA_zheevd_Tile_Async", "invalid descriptor");
        return plasma_request_fail(sequence, request, PLASMA_ERR_ILLEGAL_VALUE);
    } else {
        descA = *A;
    }
    if (plasma_desc_check(T) != PLASMA_SUCCESS) {
        plasma_error("PLASMA_zheevd_Tile_Async", "invalid descriptor");
        return plasma_request_fail(sequence, request, PLASMA_ERR_ILLEGAL_VALUE);
    } else {
        descT = *T;
    }
    /* Check input arguments */
    if (jobz != PlasmaNoVec && jobz != PlasmaVec) {
        plasma_error("PLASMA_zheevd_Tile_Async", "illegal value of jobz");
        return plasma_request_fail(sequence, request, PLASMA_ERR_ILLEGAL_VALUE);
    }
    if (uplo != PlasmaLower && uplo != PlasmaUpper) {
        plasma_error("PLASMA_zheevd_Tile_Async", "illegal value of uplo");
        return plasma_request_fail(sequence, request, PLASMA_ERR_ILLEGAL_VALUE);
    }
    if (descA.m != descA.n) {
        plasma_error("PLASMA_zheevd_Tile_Async", "matrix need to be square");
        return plasma_request_fail(sequence, request, PLASMA_ERR_ILLEGAL_VALUE);
    }
    if (descA.nb != descA.mb) {
        plasma_error("PLASMA_zheevd_Tile_Async", "only square tiles supported");
        return plasma_request_fail(sequence, request, PLASMA_ERR_ILLEGAL_VALUE);
    }

    N    = descA.m;
    NB = min(descA.mb,descA.m);
    LDAB = 2*NB+1;

    /* Allocate workspace for band storage of the band matrix A and for the off diagonal after tridiagonalisation */
    AB = (PLASMA_Complex64_t *)plasma_shared_alloc(plasma, LDAB*N, PlasmaComplexDouble);
    memset( AB, 0, LDAB * N * sizeof(PLASMA_Complex64_t) );
    if (AB == NULL) {
        plasma_error("PLASMA_zheevd_Tile_Async", "plasma_shared_alloc(AB) failed");
        plasma_shared_free(plasma, AB);
        return PLASMA_ERR_OUT_OF_RESOURCES;
    }
    E = (double *)plasma_shared_alloc(plasma, N, PlasmaRealDouble);
    if (E == NULL) {
        plasma_error("PLASMA_zheevd_Tile_Async", "plasma_shared_alloc(E) failed");
        plasma_shared_free(plasma, E);
        return PLASMA_ERR_OUT_OF_RESOURCES;
    }
    #if defined(ENABLE_TIMER)
    PLASMA_Double_t timelpk=0.0,timeaplQ2=0.0, timeT=0.0;
    PLASMA_Double_t timeB=0.0,timeblg=0.0,timeaplQ1=0.0,timeconv1=0.0,timeconv2=0.0,timeall=0.0;
    timeall = PLASMA_Wtime();
    timeB   = PLASMA_Wtime();
    #endif
    /* Reduction to tridiagonal form
     * with a two-stage approach.
     */
    /*=======================================
     *  calling Reduction to BAND
     *  then convert matrix to band form
     *=======================================*/
    plasma_dynamic_call_5(plasma_pzhetrd_he2hb,
        PLASMA_enum, uplo,
        PLASMA_desc, descA,
        PLASMA_desc, descT,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);
    plasma_dynamic_call_6( plasma_pzhbcpy_t2bl,
        PLASMA_enum, uplo,
        PLASMA_desc, descA,
        PLASMA_Complex64_t*, AB,
        int, LDAB,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    plasma_dynamic_sync();

    status = sequence->status;
    if (status != PLASMA_SUCCESS) {
        plasma_error("PLASMA_zheevd","pzhetrd_he2hb+pzcopy");
        return status;
    }
    /*=======================================
     *  END of calling Reduction to BAND
     *=======================================*/
    #if defined(ENABLE_TIMER)
    timeB   = PLASMA_Wtime()-timeB;
    printf("\n  Finish Band red    timing= %lf \n",timeB);
    #endif
    /*=======================================
     *  calling bulge chasing
     *=======================================*/
    PLASMA_Complex64_t *TAU2 = NULL;
    PLASMA_Complex64_t *V2 = NULL;
    PLASMA_Complex64_t *T2 = NULL;
    int Vblksiz, blkcnt, LDT, LDV;
    int WANTZ   = 0;
    int blguplo = PlasmaLower;
    /* int NE      = N; // for later use when a portion of the eigenvectors are requested*/
    if( jobz == PlasmaNoVec )
        WANTZ=0;
    else
        WANTZ=2;
    /* Vblksiz correspond to the blocking used when applying V2 to the matrix Q
     * it is similar to IB in LAPACK ZUNMQR.
     * blkcnt is the number of losange or tile of Vs */
    /* Note that in case PlamaVec requested, the V2 and T2 are stored by the
     * bulgechasing function in a special format:
     * for V2s: it store the V2(LDV,Vblksiz) of each losange in a tile storage meaning
     * that V2_1 is stored then V2_2,..., V2_blkcnt.
     * blkcnt is the number of losange.
     * */
    Vblksiz = min(NB,48);
    LDT     = Vblksiz;
    if( jobz == PlasmaVec ) {
        findVTsiz(N, NB, Vblksiz, &blkcnt, &LDV);
        TAU2   = (PLASMA_Complex64_t *) plasma_shared_alloc(plasma,     blkcnt*Vblksiz, PlasmaComplexDouble);
        V2     = (PLASMA_Complex64_t *) plasma_shared_alloc(plasma, LDV*blkcnt*Vblksiz, PlasmaComplexDouble);
        T2     = (PLASMA_Complex64_t *) plasma_shared_alloc(plasma, LDT*blkcnt*Vblksiz, PlasmaComplexDouble);
        if ( (TAU2 == NULL) || (V2 == NULL) || (T2 == NULL) ) {
            plasma_error("PLASMA_zheevd", "plasma_shared_alloc() failed");
            plasma_shared_free(plasma, TAU2);
            plasma_shared_free(plasma, V2);
            plasma_shared_free(plasma, T2);
            return PLASMA_ERR_OUT_OF_RESOURCES;
        }
        memset(TAU2, 0,     blkcnt*Vblksiz*sizeof(PLASMA_Complex64_t));
        memset(V2,   0, LDV*blkcnt*Vblksiz*sizeof(PLASMA_Complex64_t));
        memset(T2,   0, LDT*blkcnt*Vblksiz*sizeof(PLASMA_Complex64_t));
    }
    else {
        TAU2   = (PLASMA_Complex64_t *) plasma_shared_alloc(plasma, 2*N, PlasmaComplexDouble);
        V2     = (PLASMA_Complex64_t *) plasma_shared_alloc(plasma, 2*N, PlasmaComplexDouble);
        if ( (TAU2 == NULL) || (V2 == NULL) ) {
            plasma_error("PLASMA_zheevd", "plasma_shared_alloc() failed");
            plasma_shared_free(plasma, TAU2);
            plasma_shared_free(plasma, V2);
            return PLASMA_ERR_OUT_OF_RESOURCES;
        }
        memset(TAU2, 0, 2*N*sizeof(PLASMA_Complex64_t));
        memset(V2,   0, 2*N*sizeof(PLASMA_Complex64_t));
    }
    #if defined(ENABLE_TIMER)
    timeblg  = PLASMA_Wtime();
    #endif
    plasma_static_call_13(plasma_pzhetrd_hb2st_v1,
        PLASMA_enum,         blguplo,
        int,                 N,
        int,                 NB,
        int,                 Vblksiz,
        PLASMA_Complex64_t*, AB,
        int,                 LDAB,
        PLASMA_Complex64_t*, V2,
        PLASMA_Complex64_t*, TAU2,
        double*,             W,
        double*,             E,
        int,                 WANTZ,
        PLASMA_sequence*,    sequence,
        PLASMA_request*,     request);
    /* WARNING: If plasma_pzhetrd_hb2st is implemented through a dynamic call, don't
     * forget to synchronize */
    plasma_dynamic_sync();
    /*=======================================
     *  END of calling bulge chasing
     *=======================================*/
    #if defined(ENABLE_TIMER)
    timeblg   = PLASMA_Wtime()-timeblg;
    printf("  Finish Bulge       timing= %lf \n" ,timeblg);
    timelpk   = PLASMA_Wtime();
    #endif
    /*=======================================
     *  calling eigensolver
     *=======================================*/
    /* call eigensolver using lapack routine for our resulting tridiag [D E] */
    plasma_setlapack_multithreads(plasma->world_size);
    /*
    status = LAPACKE_zstevd( LAPACK_COL_MAJOR, lapack_const(jobz), N, W, E, Q, LDQ );
    */
    if(jobz == PlasmaNoVec){
        status = LAPACKE_zstedc( LAPACK_COL_MAJOR, lapack_const(PlasmaNoVec),
                        N, W, E, Q, LDQ );
    } else {
        status = LAPACKE_zstedc( LAPACK_COL_MAJOR, lapack_const(PlasmaIvec),
                        N, W, E, Q, LDQ );
    }

    if(status != 0){
        plasma_error("PLASMA_zstedc","ZSTEDC");
        return status;
    }
    sequence->status = status;
    plasma_setlapack_sequential(plasma);
    /*=======================================
     *  END of calling eigensolver
     *=======================================*/
    #if defined(ENABLE_TIMER)
    timelpk = PLASMA_Wtime()-timelpk;
    printf("  Finish Eigensolver timing= %lf  WANTZ %d threads %d\n" ,timelpk, WANTZ,  plasma->world_size);
    #endif
    if (jobz == PlasmaVec){
        /*=======================================
         *  apply Q2 from the bulge
         *=======================================*/
        /* compute T2 */
        #if defined(ENABLE_TIMER)
        timeT  = PLASMA_Wtime();
        #endif
        plasma_static_call_8(plasma_pzlarft_blgtrd,
            int,                 N,
            int,                 NB,
            int,                 Vblksiz,
            PLASMA_Complex64_t*, V2,
            PLASMA_Complex64_t*, T2,
            PLASMA_Complex64_t*, TAU2,
            PLASMA_sequence*,    sequence,
            PLASMA_request*,     request);
        #if defined(ENABLE_TIMER)
        plasma_dynamic_sync();
        timeT  = PLASMA_Wtime()-timeT;
        printf("  Finish compute T2  timing= %lf \n" ,timeT);
        timeaplQ2  = PLASMA_Wtime();
        #endif
        /* apply Q2 from Left */
        plasma_static_call_14(plasma_pzunmqr_blgtrd,
            PLASMA_enum,         PlasmaLeft,
            PLASMA_enum,         PlasmaNoTrans,
            int,                 N,
            int,                 NB,
            int,                 N,
            int,                 Vblksiz,
            int,                 WANTZ,
            PLASMA_Complex64_t*, V2,
            PLASMA_Complex64_t*, T2,
            PLASMA_Complex64_t*, TAU2,
            PLASMA_Complex64_t*, Q,
            int,                 LDQ,
            PLASMA_sequence*,    sequence,
            PLASMA_request*,     request);
        #if defined(ENABLE_TIMER)
        plasma_dynamic_sync();
        timeaplQ2  = PLASMA_Wtime()-timeaplQ2;
        printf("  Finish compute Q2  timing= %lf \n" ,timeaplQ2);
        #endif
        /*=======================================
         *  apply Q1 from the reduction to band
         *=======================================*/
        /* CASE NB>N, Q1 doesn't need to be applied, only bulge chasing has been done */
        if( NB < N ){
            #if defined(ENABLE_TIMER)
            plasma_dynamic_sync();
            timeconv1   = PLASMA_Wtime();
            #endif
            if ( PLASMA_TRANSLATION == PLASMA_OUTOFPLACE ) {
                plasma_zooplap2tile( descQ, Q, NB, NB, LDQ, N, 0, 0, N, N, sequence, request, plasma_desc_mat_free(&(descQ)) );
            }else {
                plasma_ziplap2tile( descQ, Q, NB, NB, LDQ, N, 0, 0, N, N, sequence, request );
            }
            #if defined(ENABLE_TIMER)
            timeconv1    = PLASMA_Wtime()-timeconv1;
            timeaplQ1   = PLASMA_Wtime();
            #endif
            /* Accumulate the transformations from the first stage */
            if(uplo==PlasmaLower){
                plasma_dynamic_call_7(plasma_pzunmqr,
                    PLASMA_enum, PlasmaLeft,
                    PLASMA_enum, PlasmaNoTrans,
                    PLASMA_desc, plasma_desc_submatrix(descA, descA.mb, 0, descA.m-descA.mb, descA.n-descA.nb),
                    PLASMA_desc, plasma_desc_submatrix(descQ, descQ.mb, 0, descQ.m-descQ.mb, descQ.n),
                    PLASMA_desc, plasma_desc_submatrix(descT, descT.mb, 0, descT.m-descT.mb, descT.n-descT.nb),
                    PLASMA_sequence*, sequence,
                    PLASMA_request*, request);

            }
            else {
                plasma_dynamic_call_7(plasma_pzunmlq,
                    PLASMA_enum, PlasmaLeft,
                    PLASMA_enum, PlasmaConjTrans,
                    PLASMA_desc, plasma_desc_submatrix(descA, 0, descA.nb, descA.m-descA.mb, descA.n-descA.nb),
                    PLASMA_desc, plasma_desc_submatrix(descQ, descQ.mb, 0, descQ.m-descQ.mb, descQ.n         ),
                    PLASMA_desc, plasma_desc_submatrix(descT, 0, descT.nb, descT.m-descT.mb, descT.n-descT.nb),
                    PLASMA_sequence*, sequence,
                    PLASMA_request*, request);
            }
            #if defined(ENABLE_TIMER)
            plasma_dynamic_sync();
            timeaplQ1   = PLASMA_Wtime()-timeaplQ1;
            printf("  Finish compute Q1  timing= %lf \n" ,timeaplQ1);
            timeconv2    = PLASMA_Wtime();
            #endif
            if ( PLASMA_TRANSLATION == PLASMA_OUTOFPLACE ) {
                plasma_zooptile2lap( descQ, Q, NB, NB, LDQ, N, sequence, request );
                plasma_dynamic_sync();
                plasma_desc_mat_free(&descQ);
            } else {
                plasma_ziptile2lap( descQ, Q, NB, NB, LDQ, N, sequence, request );
                plasma_dynamic_sync();
            }
            #if defined(ENABLE_TIMER)
            plasma_dynamic_sync();
            timeconv2    = PLASMA_Wtime()-timeconv2;
            printf("  Finish convert     timing= %lf \n" ,timeconv1+timeconv2);
            #endif
        } /* END of ( NB < N ) */
    }
    #if defined(ENABLE_TIMER)
    timeall = PLASMA_Wtime()-timeall;
    printf("  Finish full eigenproblem threads %d  N %d  timeall= %lf \n", plasma->world_size, N, timeall);
    #endif

    if( jobz == PlasmaVec )
        plasma_shared_free(plasma, T2);
    plasma_shared_free(plasma, V2);
    plasma_shared_free(plasma, TAU2);
    plasma_shared_free(plasma, E);
    plasma_shared_free(plasma, AB);
    return PLASMA_SUCCESS;
}


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C header for functions windowing and computing FFT/IFFT of time/frequency series.
 *
 */

#ifndef __GENERATELLVFD_H__
#define __GENERATELLVFD_H__ 1

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#define _XOPEN_SOURCE 500

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"
#include "struct.h"
#include "waveform.h"
#include "fft.h"
#include "LLVgeometry.h"
#include "LLVFDresponse.h"

/********************************** Structures ******************************************/

typedef enum GenLLVFDtag {
  LLVFD,
  LLVhlm
} GenLLVFDtag;

/* Parameters for the generation of a LLV waveform (in the form of a list of modes) */
typedef struct tagGenLLVFDparams {
  double tRef;               /* reference time (s) - GPS time at the frequency representing coalescence */
  double phiRef;             /* reference phase (rad) - phase at the frequency representing coalescence (or at fRef if specified) */
  double fRef;               /* reference frequency at which phiRef is set (Hz, default 0 which is interpreted as Mf=0.14) */
  double m1;                 /* mass of companion 1 (solar masses, default 2e6) */
  double m2;                 /* mass of companion 2 (solar masses, default 1e6) */
  double distance;           /* distance of source (Mpc, default 1e3) */
  double inclination;        /* inclination of source (rad, default pi/3) */
  double ra;                 /* first angle for the position in the sky (rad, default 0) */
  double dec;                /* second angle for the position in the sky (rad, default 0) */
  double polarization;       /* polarization angle (rad, default 0) */

  int nbmode;                /* number of modes to generate (starting with 22) - defaults to 5 (all modes) */
  double minf;               /* Minimal frequency, ignore if 0 (Hz, default=0) - will use first frequency covered by the ROM if higher */
  double maxf;               /* Maximal frequency, ignore if 0 (Hz, default=0) - will use last frequency covered by the ROM if lower */
  double deltaf;             /* When generating frequency series from the mode contributions, deltaf for the output (0 to set automatically at 1/2*1/(2T)) */
  int tagnetwork;            /* Tag selecting the desired output format */
  int taggenwave;            /* Tag selecting the desired output format */
  int fromLLVtdfile;         /* Tag for loading time series for LLV detectors and FFTing */
  int nsamplesinfile;        /* Number of lines of input file */
  int binaryin;              /* Tag for loading the data in gsl binary form instead of text (default false) */
  int binaryout;             /* Tag for outputting the data in gsl binary form instead of text (default false) */
  char indir[256];           /* Path for the input directory */
  char infile[256];          /* Path for the input file */
  char outdir[256];          /* Path for the output directory */
  char outfileprefix[256];   /* Path for the output file */
} GenLLVFDparams;

#if 0
{ /* so that editors will match succeeding brace */
#elif defined(__cplusplus)
}
#endif

#endif /* _GENERATELLVFD_H */


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

void
CORE_zlantr_quark(Quark *quark);
void
CORE_zlantr_f1_quark(Quark *quark);

/***************************************************************************//**
 *
 **/
void QUARK_CORE_zlantr(Quark *quark, Quark_Task_Flags *task_flags,
                       PLASMA_enum norm, PLASMA_enum uplo, PLASMA_enum diag, int M, int N,
                       const PLASMA_Complex64_t *A, int LDA, int szeA,
                       int szeW, double *result)
{
    szeW = max(1, szeW);
    DAG_CORE_LANGE;
    QUARK_Insert_Task(quark, CORE_zlantr_quark, task_flags,
        sizeof(PLASMA_enum),                &norm,  VALUE,
        sizeof(PLASMA_enum),                &uplo,  VALUE,
        sizeof(PLASMA_enum),                &diag,  VALUE,
        sizeof(int),                        &M,     VALUE,
        sizeof(int),                        &N,     VALUE,
        sizeof(PLASMA_Complex64_t)*szeA,     A,             INPUT,
        sizeof(int),                        &LDA,   VALUE,
        sizeof(double)*szeW,                 NULL,          SCRATCH,
        sizeof(double),                      result,        OUTPUT,
        0);
}

/***************************************************************************//**
 *
 **/
void QUARK_CORE_zlantr_f1(Quark *quark, Quark_Task_Flags *task_flags,
                          PLASMA_enum norm, PLASMA_enum uplo, PLASMA_enum diag, int M, int N,
                          const PLASMA_Complex64_t *A, int LDA, int szeA,
                          int szeW, double *result,
                          double *fake, int szeF)
{
    szeW = max(1, szeW);
    DAG_CORE_LANGE;

    if ( result == fake ) {
        QUARK_Insert_Task(quark, CORE_zlantr_quark, task_flags,
            sizeof(PLASMA_enum),                &norm,  VALUE,
            sizeof(PLASMA_enum),                &uplo,  VALUE,
            sizeof(PLASMA_enum),                &diag,  VALUE,
            sizeof(int),                        &M,     VALUE,
            sizeof(int),                        &N,     VALUE,
            sizeof(PLASMA_Complex64_t)*szeA,     A,             INPUT,
            sizeof(int),                        &LDA,   VALUE,
            sizeof(double)*szeW,                 NULL,          SCRATCH,
            sizeof(double),                      result,        OUTPUT | GATHERV,
            0);
    } else {
        QUARK_Insert_Task(quark, CORE_zlantr_f1_quark, task_flags,
            sizeof(PLASMA_enum),                &norm,  VALUE,
            sizeof(PLASMA_enum),                &uplo,  VALUE,
            sizeof(PLASMA_enum),                &diag,  VALUE,
            sizeof(int),                        &M,     VALUE,
            sizeof(int),                        &N,     VALUE,
            sizeof(PLASMA_Complex64_t)*szeA,     A,             INPUT,
            sizeof(int),                        &LDA,   VALUE,
            sizeof(double)*szeW,                 NULL,          SCRATCH,
            sizeof(double),                      result,        OUTPUT,
            sizeof(double)*szeF,                 fake,          OUTPUT | GATHERV,
            0);
    }
}

/***************************************************************************//**
 *
 **/
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_zlantr_quark = PCORE_zlantr_quark
#define CORE_zlantr_quark PCORE_zlantr_quark
#endif
void CORE_zlantr_quark(Quark *quark)
{
    double *normA;
    PLASMA_enum norm, uplo, diag;
    int M;
    int N;
    PLASMA_Complex64_t *A;
    int LDA;
    double *work;

    quark_unpack_args_9(quark, norm, uplo, diag, M, N, A, LDA, work, normA);
    *normA = LAPACKE_zlantr_work(
        LAPACK_COL_MAJOR,
        lapack_const(norm),
        lapack_const(uplo),
        lapack_const(diag),
        M, N, A, LDA, work);
}

/***************************************************************************//**
 *
 **/
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_zlantr_f1_quark = PCORE_zlantr_f1_quark
#define CORE_zlantr_f1_quark PCORE_zlantr_f1_quark
#endif
void CORE_zlantr_f1_quark(Quark *quark)
{
    double *normA;
    PLASMA_enum norm, uplo, diag;
    int M;
    int N;
    PLASMA_Complex64_t *A;
    int LDA;
    double *work;
    double *fake;

    quark_unpack_args_10(quark, norm, uplo, diag, M, N, A, LDA, work, normA, fake);
    *normA = LAPACKE_zlantr_work(
        LAPACK_COL_MAJOR,
        lapack_const(norm),
        lapack_const(uplo),
        lapack_const(diag),
        M, N, A, LDA, work);
}


//
// Created by daeyun on 4/11/17.
//

#pragma once

#ifndef NDEBUG
#define USE_OMP false
#else
#define USE_OMP true
#endif

#include <iostream>
#include <limits>
#include <cstdlib>
#include <vector>
#include <array>
#include <string>
#include <map>
#include <set>
#include <memory>

#include <gsl/gsl_assert>
#include <Eigen/Dense>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"

using Eigen::Matrix;
using Eigen::MatrixXd;
using Eigen::Dynamic;

using Vec = Eigen::VectorXd;
using Vec2 = Eigen::Vector2d;
using Vec2i = Eigen::Matrix<int, 2, 1>;
using Vec3 = Eigen::Vector3d;
using Vec4 = Eigen::Vector4d;
using Mat44 = Eigen::Matrix<double, 4, 4>;
using Mat34 = Eigen::Matrix<double, 3, 4>;
using Mat33 = Eigen::Matrix<double, 3, 3>;
using MatrixX = Eigen::MatrixXd;

using Points4d = Eigen::Matrix<double, 4, Eigen::Dynamic>;
using Points3d = Eigen::Matrix<double, 3, Eigen::Dynamic>;
using Points2d = Eigen::Matrix<double, 2, Eigen::Dynamic>;
using Points2i = Eigen::Matrix<int, 2, Eigen::Dynamic>;
using Points1d = Eigen::Matrix<double, 1, Eigen::Dynamic>;

using std::vector;
using std::array;
using std::string;
using std::unique_ptr;
using std::shared_ptr;
using std::make_unique;
using std::make_shared;
using std::move;
using std::tuple;
using std::pair;
using std::map;
using std::set;
using std::get;

constexpr double kInfinity = std::numeric_limits<double>::infinity();
constexpr double kOneOverTwoPi = M_1_PI * 0.5;
constexpr double kEpsilon = 1e-9;
constexpr double kDeg2Rad = M_PI / 180.0;

// Make sure to have the following in the executable: spdlog::stdout_color_mt("console");
#define LOGGER spdlog::get("console")


#ifndef CONVERTERS_H_HVFGCFVK
#define CONVERTERS_H_HVFGCFVK

#include <fmt/core.h>
#include <gsl/gsl>
#include <opencv2/imgcodecs.hpp>
#include <sens_loc/conversion/depth_to_bearing.h>
#include <sens_loc/conversion/depth_to_curvature.h>
#include <sens_loc/conversion/depth_to_flexion.h>
#include <sens_loc/conversion/depth_to_laserscan.h>
#include <sens_loc/conversion/depth_to_max_curve.h>
#include <util/batch_converter.h>

namespace sens_loc::apps {

/// \addtogroup conversion-driver
/// @{

/// Batch conversion to bearing-angle images.
/// \sa conversion::depth_to_bearing
template <typename Intrinsic>
class bearing_converter : public batch_sensor_converter<Intrinsic> {
  public:
    bearing_converter(const file_patterns& files,
                      depth_type           t,
                      Intrinsic            intrinsic)
        : batch_sensor_converter<Intrinsic>(files, t, std::move(intrinsic)) {
        if (files.horizontal.empty() && files.vertical.empty() &&
            files.diagonal.empty() && files.antidiagonal.empty()) {
            throw std::invalid_argument{
                "Missing output pattern for at least one bearing direction"};
        }
    }
    bearing_converter(const bearing_converter&) = default;
    bearing_converter(bearing_converter&&)      = default;
    bearing_converter& operator=(const bearing_converter&) = default;
    bearing_converter& operator=(bearing_converter&&) = default;
    ~bearing_converter() override                     = default;

  private:
    [[nodiscard]] bool process_file(const math::image<float>& depth_image,
                                    int idx) const noexcept override;
};
#include "converter_bearing.h.inl"

/// Convert orthographic depth-images to range (laserscan-like) images.
/// \sa conversion::depth_to_laserscan
template <typename Intrinsic>
class range_converter : public batch_sensor_converter<Intrinsic> {
  public:
    range_converter(const file_patterns& files,
                    depth_type           t,
                    Intrinsic            intrinsic)
        : batch_sensor_converter<Intrinsic>(files, t, std::move(intrinsic)) {}
    range_converter(const range_converter&) = default;
    range_converter(range_converter&&)      = default;
    range_converter& operator=(const range_converter&) = default;
    range_converter& operator=(range_converter&&) = default;
    ~range_converter() override                   = default;

  private:
    [[nodiscard]] bool process_file(const math::image<float>& depth_image,
                                    int idx) const noexcept override;
};
#include "converter_laserscan.h.inl"

/// Convert range-images to gaussian curvature images.
/// \sa conversion::depth_to_gaussian_curvature
template <typename Intrinsic>
class gauss_curv_converter : public batch_sensor_converter<Intrinsic> {
  public:
    /// \param files,t,intrinsic normal parameters for batch conversion
    /// \param lower_bound,upper_bound clamping parameters. Values below/above
    /// will map to these values.
    gauss_curv_converter(const file_patterns& files,
                         depth_type           t,
                         Intrinsic            intrinsic,
                         double               lower_bound,
                         double               upper_bound)
        : batch_sensor_converter<Intrinsic>(files, t, std::move(intrinsic))
        , lower_bound{lower_bound}
        , upper_bound{upper_bound} {}
    gauss_curv_converter(const gauss_curv_converter&) = default;
    gauss_curv_converter(gauss_curv_converter&&)      = default;
    gauss_curv_converter& operator=(const gauss_curv_converter&) = default;
    gauss_curv_converter& operator=(gauss_curv_converter&&) = default;
    ~gauss_curv_converter() override                        = default;

  private:
    [[nodiscard]] bool process_file(const math::image<float>& depth_image,
                                    int idx) const noexcept override;

    double lower_bound;
    double upper_bound;
};

/// Convert range-images to mean curvature images.
/// \sa conversion::depth_to_mean_curvature
template <typename Intrinsic>
class mean_curv_converter : public batch_sensor_converter<Intrinsic> {
  public:
    /// \param files,t,intrinsic normal parameters for batch conversion
    /// \param lower_bound,upper_bound clamping parameters. Values
    /// below/above will map to these values.
    mean_curv_converter(const file_patterns& files,
                        depth_type           t,
                        Intrinsic            intrinsic,
                        double               lower_bound,
                        double               upper_bound)
        : batch_sensor_converter<Intrinsic>(files, t, std::move(intrinsic))
        , lower_bound{lower_bound}
        , upper_bound{upper_bound} {}
    mean_curv_converter(const mean_curv_converter&) = default;
    mean_curv_converter(mean_curv_converter&&)      = default;
    mean_curv_converter& operator=(const mean_curv_converter&) = default;
    mean_curv_converter& operator=(mean_curv_converter&&) = default;
    ~mean_curv_converter() override                       = default;

  private:
    [[nodiscard]] bool process_file(const math::image<float>& depth_image,
                                    int idx) const noexcept override;

    double lower_bound;
    double upper_bound;
};
#include "converter_curvature.h.inl"

/// Convert range-images to max-curve images.
/// \sa conversion::depth_to_max_curve
template <typename Intrinsic>
class max_curve_converter : public batch_sensor_converter<Intrinsic> {
  public:
    max_curve_converter(const file_patterns& files,
                        depth_type           t,
                        Intrinsic            intrinsic)
        : batch_sensor_converter<Intrinsic>(files, t, std::move(intrinsic)) {}
    max_curve_converter(const max_curve_converter&) = default;
    max_curve_converter(max_curve_converter&&)      = default;
    max_curve_converter& operator=(const max_curve_converter&) = default;
    max_curve_converter& operator=(max_curve_converter&&) = default;
    ~max_curve_converter() override                       = default;

  private:
    [[nodiscard]] bool process_file(const math::image<float>& depth_image,
                                    int idx) const noexcept override;
};
#include "converter_max_curve.h.inl"

/// Convert range-images to flexion images.
/// \sa conversion::depth_to_flexion
template <typename Intrinsic>
class flexion_converter : public batch_sensor_converter<Intrinsic> {
  public:
    flexion_converter(const file_patterns& files,
                      depth_type           t,
                      Intrinsic            intrinsic)
        : batch_sensor_converter<Intrinsic>(files, t, std::move(intrinsic)) {}
    flexion_converter(const flexion_converter&) = default;
    flexion_converter(flexion_converter&&)      = default;
    flexion_converter& operator=(const flexion_converter&) = default;
    flexion_converter& operator=(flexion_converter&&) = default;
    ~flexion_converter() override                     = default;

  private:
    [[nodiscard]] bool process_file(const math::image<float>& depth_image,
                                    int idx) const noexcept override;
};
#include "converter_flexion.h.inl"

/// @}

}  // namespace sens_loc::apps

#endif


/*
 * Copyright (C) 2015  University of Oregon
 *
 * You may distribute under the terms of either the GNU General Public
 * License or the Apache License, as specified in the LICENSE file.
 *
 * For more information, see the LICENSE file.
 */
/*---------------------------------------------------------------------------*/
/* This is free software: you can redistribute it and/or modify              */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, either version 3 of the License, or         */
/* (at your option) any later version.                                       */
/*                                                                           */
/* This is distributed in the hope that it will be useful,                   */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* If not, see <http://www.gnu.org/licenses/>.                               */
/*---------------------------------------------------------------------------*/
/**/

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <stddef.h>
#include <sys/time.h>

#define TRUE       1
#define FALSE      0

#define MAXSTR 1024
#define MAXELEM 1024

/* Some data types */
#define INT16      1  /* 16 bit integer */
#define INT32      2  /* 32 bit integer */
#define FLT32      3  /* 32 bit float */
#define DBL64      4  /* 64 bit double */

/* Include GSL libraries */
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_eigen.h>

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_multifit_nlin.h>

/* Include local functions */
#include "gsl_functions.c"  /* local GSL functions */
#include "utils.c"
#include "getpars.c"
#include "fdf.c"
#include "image_functions.c"

/* Include Analyze libraries */
#include "AVW.c"  /* local AVW functions */

#ifdef NOTLIKELY
#include <AVW.h>
#include <AVW_ImageFile.h>
#include <AVW.c>
#endif
//#include "avw.c"


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

#include <gsl/gsl_interp.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_errno.h>

#include "ccl.h"

ccl_a_finder *ccl_a_finder_new(int na, double *a_arr)
{
  if(na<=0)
    return NULL;

  ccl_a_finder *finda=malloc(sizeof(ccl_a_finder));
  if(finda == NULL)
    return NULL;

  finda->ia_last=0;
  finda->amin = a_arr[0];
  finda->amax = a_arr[na-1];
  finda->na = na;
  finda->a_arr = malloc(na*sizeof(double));
  if(finda->a_arr==NULL) {
    free(finda);
    return NULL;
  }

  memcpy(finda->a_arr, a_arr, na*sizeof(double));

  return finda;
}

void ccl_a_finder_free(ccl_a_finder *finda)
{
  if(finda!=NULL) {
    if(finda->na>0)
      free(finda->a_arr);
    free(finda);
  }
}

int ccl_find_a_index(ccl_a_finder *finda, double a)
{
  int ia_0;

  if(a>=finda->amax)
    ia_0=finda->na-1;
  else if(a<=finda->amin)
    ia_0=0;
  else {
    int gotit=0;
    ia_0=finda->ia_last;
    while(!gotit) {
      if(ia_0==0) {
        if(a<finda->a_arr[1])
          gotit=1;
        else
          ia_0++;
      }
      else if(ia_0==finda->na-1) {
        if(a>=finda->a_arr[ia_0-1])
          gotit=1;
        ia_0--;
      }
      else {
        if(a<finda->a_arr[ia_0])
          ia_0--;
        else {
          if(a>=finda->a_arr[ia_0+1])
            ia_0++;
          else
            gotit=1;
        }
      }
    }
  }

  finda->ia_last = ia_0;
  return ia_0;
}

ccl_f3d_t *ccl_f3d_t_copy(ccl_f3d_t *f3d_o, int *status)
{
  int ia, s2dstatus=0;
  ccl_f3d_t *f3d = malloc(sizeof(ccl_f3d_t));
  if (f3d == NULL)
    *status = CCL_ERROR_MEMORY;

  if(*status==0) {
    f3d->lkmin = f3d_o->lkmin;
    f3d->lkmax = f3d_o->lkmax;
    f3d->na = f3d_o->na;
    f3d->is_product = f3d_o->is_product;
    f3d->extrap_linear_growth = f3d_o->extrap_linear_growth;
    f3d->extrap_order_lok = f3d_o->extrap_order_lok;
    f3d->extrap_order_hik = f3d_o->extrap_order_hik;
    f3d->is_log = f3d_o->is_log;
    f3d->growth_factor_0 = f3d_o->growth_factor_0;
    f3d->growth_exponent = f3d_o->growth_exponent;

    f3d->a_arr = malloc(f3d->na*sizeof(double));
    if(f3d->a_arr == NULL)
      *status = CCL_ERROR_MEMORY;
  }

  if(*status==0) {
    memcpy(f3d->a_arr, f3d_o->a_arr, f3d->na*sizeof(double));

    if(f3d_o->fka_1 != NULL)
      f3d->fka_1 = ccl_f2d_t_copy(f3d_o->fka_1, status);
    else
      f3d->fka_1 = NULL;
  }

  if(*status==0) {
    if(f3d_o->fka_2 != NULL)
      f3d->fka_2 = ccl_f2d_t_copy(f3d_o->fka_2, status);
    else
      f3d->fka_2 = NULL;
  }

  if(*status==0) {
    if(f3d_o->tkka != NULL) {
      f3d->tkka = malloc(f3d->na*sizeof(gsl_spline2d));
      if (f3d->tkka == NULL)
        *status = CCL_ERROR_MEMORY;

      if(*status == 0) {
        s2dstatus = 0;
        for(ia=0; ia<f3d->na; ia++) {
          f3d->tkka[ia] = gsl_spline2d_alloc(gsl_interp2d_bicubic,
                                             f3d_o->tkka[ia]->interp_object.xsize,
                                             f3d_o->tkka[ia]->interp_object.ysize);
          if(f3d->tkka[ia] == NULL)
            *status = CCL_ERROR_MEMORY;

          if(*status==0) {
            s2dstatus |= gsl_spline2d_init(f3d->tkka[ia],
                                           f3d_o->tkka[ia]->xarr,
                                           f3d_o->tkka[ia]->yarr,
                                           f3d_o->tkka[ia]->zarr,
                                           f3d_o->tkka[ia]->interp_object.xsize,
                                           f3d_o->tkka[ia]->interp_object.ysize);
            if(s2dstatus)
              *status = CCL_ERROR_SPLINE;
          }
        }
      }
    }
    else
      f3d->tkka = NULL;
  }

  return f3d;
}

ccl_f3d_t *ccl_f3d_t_new(int na,double *a_arr,
                         int nk,double *lk_arr,
                         double *tkka_arr,
                         double *fka1_arr,
                         double *fka2_arr,
                         int is_product,
                         int extrap_order_lok,
                         int extrap_order_hik,
                         ccl_f2d_extrap_growth_t extrap_linear_growth,
                         int is_tkka_log,
                         double growth_factor_0,
                         int growth_exponent,
                         ccl_f2d_interp_t interp_type,
                         int *status) {
  int ia, s2dstatus;
  ccl_f3d_t *f3d = malloc(sizeof(ccl_f3d_t));
  if (f3d == NULL)
    *status = CCL_ERROR_MEMORY;

  if (*status == 0) {
    is_product = is_product || (tkka_arr == NULL);
    f3d->is_product = is_product;
    f3d->extrap_order_lok = extrap_order_lok;
    f3d->extrap_order_hik = extrap_order_hik;
    f3d->extrap_linear_growth = extrap_linear_growth;
    f3d->is_log = is_tkka_log;
    f3d->growth_factor_0 = growth_factor_0;
    f3d->growth_exponent = growth_exponent;
    f3d->fka_1 = NULL;
    f3d->fka_2 = NULL;
    f3d->tkka = NULL;

    f3d->lkmin = lk_arr[0];
    f3d->lkmax = lk_arr[nk-1];
    f3d->na = na;
    f3d->a_arr = malloc(na*sizeof(double));
    if(f3d->a_arr == NULL)
      *status = CCL_ERROR_MEMORY;
  }

  if (*status == 0)
    memcpy(f3d->a_arr, a_arr, na*sizeof(double));

  if ((extrap_order_lok > 1) || (extrap_order_lok < 0) ||
      (extrap_order_hik > 1) || (extrap_order_hik < 0))
    *status = CCL_ERROR_INCONSISTENT;

  if ((extrap_linear_growth != ccl_f2d_cclgrowth) &&
      (extrap_linear_growth != ccl_f2d_constantgrowth) &&
      (extrap_linear_growth != ccl_f2d_no_extrapol))
    *status = CCL_ERROR_INCONSISTENT;

  if (f3d->is_product) {
    if ((fka1_arr == NULL) || (fka2_arr == NULL))
      *status = CCL_ERROR_INCONSISTENT;
  }
  else {
    if (tkka_arr == NULL)
      *status = CCL_ERROR_INCONSISTENT;
  }

  if(*status == 0) {
    if (f3d->is_product) {
      f3d->fka_1 = ccl_f2d_t_new(na, a_arr,
                                 nk, lk_arr,
                                 fka1_arr, NULL, NULL, 0,
                                 extrap_order_lok, extrap_order_hik,
                                 extrap_linear_growth,
                                 is_tkka_log,
                                 growth_factor_0, growth_exponent/2,
                                 interp_type, status);
      f3d->fka_2 = ccl_f2d_t_new(na, a_arr,
                                 nk, lk_arr,
                                 fka2_arr, NULL, NULL, 0,
                                 extrap_order_lok, extrap_order_hik,
                                 extrap_linear_growth,
                                 is_tkka_log,
                                 growth_factor_0, growth_exponent/2,
                                 interp_type, status);
    }
    else {
      switch(interp_type) {
      case(ccl_f2d_3):
        f3d->tkka = malloc(na*sizeof(gsl_spline2d));
        if (f3d->tkka == NULL)
          *status = CCL_ERROR_MEMORY;
        if(*status == 0) {
          for(ia=0; ia<na; ia++) {
            double *tkk = &(tkka_arr[ia*nk*nk]);
            f3d->tkka[ia] = gsl_spline2d_alloc(gsl_interp2d_bicubic, nk, nk);
            if (f3d->tkka[ia] == NULL) {
              *status = CCL_ERROR_MEMORY;
              break;
            }
            s2dstatus = gsl_spline2d_init(f3d->tkka[ia],
                                          lk_arr, lk_arr, tkk, nk, nk);
            if (s2dstatus) {
              *status = CCL_ERROR_SPLINE;
              break;
            }
          }
        }
        break;
      default:
        f3d->tkka = NULL;
      }
    }
  }

  return f3d;
}

double ccl_f3d_t_eval(ccl_f3d_t *f3d,double lk1,double lk2,double a,ccl_a_finder *finda,
                      void *cosmo, int *status) {
  double tkka_post;

  double a_ev = a;
  int is_hiz = a < f3d->a_arr[0];
  int is_loz = a > f3d->a_arr[f3d->na-1];
  if (is_loz) { // Are we above the interpolation range in a?
    if (f3d->extrap_linear_growth == ccl_f2d_no_extrapol) {
      *status=CCL_ERROR_SPLINE_EV;
      return NAN;
    }
    a_ev = f3d->a_arr[f3d->na-1];
  }
  else if (is_hiz) { // Are we below the interpolation range in a?
    if (f3d->extrap_linear_growth == ccl_f2d_no_extrapol) {
      *status=CCL_ERROR_SPLINE_EV;
      return NAN;
    }
    a_ev = f3d->a_arr[0];
  }

  if (f3d->is_product) {
    double fka1 = ccl_f2d_t_eval(f3d->fka_1, lk1, a_ev, cosmo, status);
    double fka2 = ccl_f2d_t_eval(f3d->fka_2, lk2, a_ev, cosmo, status);
    if(*status != 0)
      return NAN;
    tkka_post = fka1*fka2;
  }
  else {
    double lk1_ev = lk1;
    int is_hik1 = lk1 > f3d->lkmax;
    int is_lok1 = lk1 < f3d->lkmin;
    int extrap_k1 = (is_hik1 & (f3d->extrap_order_hik > 0)) ||  (is_lok1 & (f3d->extrap_order_lok > 0));
    if (is_hik1) // Are we above the interpolation range in k?
      lk1_ev = f3d->lkmax;
    else if (is_lok1) // Are we below the interpolation range in k?
      lk1_ev = f3d->lkmin;

    double lk2_ev = lk2;
    int is_hik2 = lk2 > f3d->lkmax;
    int is_lok2 = lk2 < f3d->lkmin;
    int extrap_k2 = (is_hik2 & (f3d->extrap_order_hik > 0)) ||  (is_lok2 & (f3d->extrap_order_lok > 0));
    if (is_hik2) // Are we above the interpolation range in k?
      lk2_ev = f3d->lkmax;
    else if (is_lok2) // Are we below the interpolation range in k?
      lk2_ev = f3d->lkmin;

    int ia = ccl_find_a_index(finda, a_ev);

    if(*status == 0) {
      int spstatus = 0;
      double tkka, dtkka1, dtkka2;
      spstatus |= gsl_spline2d_eval_e(f3d->tkka[ia], lk1_ev, lk2_ev,
                                      NULL, NULL, &tkka);
      if(extrap_k1)
        spstatus |= gsl_spline2d_eval_deriv_x_e(f3d->tkka[ia], lk1_ev, lk2_ev,
                                                NULL, NULL, &dtkka1);
      if(extrap_k2)
        spstatus |= gsl_spline2d_eval_deriv_y_e(f3d->tkka[ia], lk1_ev, lk2_ev,
                                                NULL, NULL, &dtkka2);
      if(ia < f3d->na-1) {
        double tkka_p1, dtkka1_p1, dtkka2_p1;
        double h = (a_ev-f3d->a_arr[ia])/(f3d->a_arr[ia+1]-f3d->a_arr[ia]);

        spstatus |= gsl_spline2d_eval_e(f3d->tkka[ia+1], lk1_ev, lk2_ev,
                                        NULL, NULL, &tkka_p1);
        if(!spstatus)
          tkka = tkka*(1-h) + tkka_p1*h;

        if(extrap_k1) {
          spstatus |= gsl_spline2d_eval_deriv_x_e(f3d->tkka[ia+1], lk1_ev, lk2_ev,
                                                  NULL, NULL, &dtkka1_p1);
          if(!spstatus)
            dtkka1 = dtkka1*(1-h) + dtkka1_p1*h;
        }

        if(extrap_k2) {
          spstatus |= gsl_spline2d_eval_deriv_y_e(f3d->tkka[ia+1], lk1_ev, lk2_ev,
                                                  NULL, NULL, &dtkka2_p1);
          if(!spstatus)
            dtkka2 = dtkka2*(1-h) + dtkka2_p1*h;
        }
      }
      if(spstatus) {
        *status = CCL_ERROR_SPLINE_EV;
        return NAN;
      }

      // Extrapolate if needed
      if(extrap_k1)
        tkka += dtkka1 * (lk1 - lk1_ev);
      if(extrap_k2)
        tkka += dtkka2 * (lk2 - lk2_ev);
      tkka_post = tkka;
    }

    // Exponentiate if needed
    if (f3d->is_log)
      tkka_post = exp(tkka_post);
  }

  // Extrapolate in a if needed
  if (is_hiz) {
    double gz;
    if (f3d->extrap_linear_growth == ccl_f2d_cclgrowth) { // Use CCL's growth function
      ccl_cosmology *csm = (ccl_cosmology *)cosmo;
      if (!csm->computed_growth) {
        *status = CCL_ERROR_GROWTH_INIT;
        ccl_cosmology_set_status_message(
          csm,
          "ccl_f3d.c: ccl_f3d_t_eval(): growth factor splines have not been precomputed!");
        return NAN;
      }
      gz = (
        ccl_growth_factor(csm, a, status) /
        ccl_growth_factor(csm, a_ev, status));
    }
    else // Use constant growth factor
      gz = f3d->growth_factor_0;

    tkka_post *= pow(gz, f3d->growth_exponent);
  }

  return tkka_post;
}

void ccl_f3d_t_free(ccl_f3d_t *f3d)
{
  if(f3d != NULL) {
    if(f3d->fka_1 != NULL)
      ccl_f2d_t_free(f3d->fka_1);
    if(f3d->fka_2 != NULL)
      ccl_f2d_t_free(f3d->fka_2);
    if(f3d->tkka != NULL) {
      int ia;
      for(ia=0; ia<f3d->na; ia++)
        gsl_spline2d_free(f3d->tkka[ia]);
      free(f3d->tkka);
    }
    if(f3d->na > 0)
      free(f3d->a_arr);
    free(f3d);
  }
}

ccl_a_finder *ccl_a_finder_new_from_f3d(ccl_f3d_t *f3d)
{
  return ccl_a_finder_new(f3d->na, f3d->a_arr);
}


/**
 *
 * @file
 *
 *  PLASMA is a software package provided by:
 *  University of Tennessee, US,
 *  University of Manchester, UK.
 *
 **/
#ifndef PLASMA_CORE_LAPACK_H
#define PLASMA_CORE_LAPACK_H

#if defined(HAVE_MKL) || defined(PLASMA_WITH_MKL)
    #define MKL_Complex16 double _Complex
    #define MKL_Complex8  float _Complex

    #include <mkl_cblas.h>
    #include <mkl_lapacke.h>

    // MKL LAPACKE doesn't provide LAPACK_GLOBAL macro, so define it here.
    // MKL provides all 3 name manglings (foo, foo_, FOO); pick foo_.
    #ifndef LAPACK_GLOBAL
    #define LAPACK_GLOBAL(lcname,UCNAME)  lcname##_
    #endif
#elif defined(HAVE_ESSL) || defined(PLASMA_WITH_ESSL)
    // GCC + ESSL(BLAS) + LAPACKE/CBLAS from LAPACK
    #include <cblas.h>
    #include <lapacke.h>

    #ifndef LAPACK_GLOBAL
    #define LAPACK_GLOBAL(lcname,UCNAME) lcname##_
    #endif
#else
    #include <cblas.h>
    #include <lapacke.h>

    // Original  cblas.h does: enum CBLAS_ORDER {...};
    // Intel mkl_cblas.h does: typedef enum {...} CBLAS_ORDER;
    // LAPACK    cblas.h does: typedef enum {...} CBLAS_ORDER;
    // OpenBLAS  cblas.h does: typedef enum CBLAS_ORDER {...} CBLAS_ORDER;
    // We use (CBLAS_ORDER), so add these typedefs for original cblas.h
    #ifdef CBLAS_ADD_TYPEDEF
    typedef enum CBLAS_ORDER CBLAS_ORDER;
    typedef enum CBLAS_TRANSPOSE CBLAS_TRANSPOSE;
    typedef enum CBLAS_UPLO CBLAS_UPLO;
    typedef enum CBLAS_DIAG CBLAS_DIAG;
    typedef enum CBLAS_SIDE CBLAS_SIDE;
    #endif
#endif

#ifndef lapack_int
#define lapack_int int
#endif

#include "core_lapack_s.h"
#include "core_lapack_d.h"
#include "core_lapack_c.h"
#include "core_lapack_z.h"

#endif // PLASMA_CORE_LAPACK_H