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

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

#ifdef RADTRANSFER

#ifdef EDDINGTON_TENSOR_STARS
static struct stardata_in
{
  MyDouble Pos[3], Density, Mass;
  MyFloat Hsml;
  int NodeList[NODELISTLENGTH];
}
 *StarDataIn, *StarDataGet;
#endif

void star_density(void)
{
  int j;

#ifdef EDDINGTON_TENSOR_STARS
  int i, dummy;
  int ngrp, sendTask, recvTask, place, nexport, nimport, ndone, ndone_flag;
#endif

  /* clear Je in all gas particles */

  for(j = 0; j < N_gas; j++)
    {
      if(P[j].Type == 0)
	SphP[j].Je = 0;

#ifdef SFR
      if(P[j].Type == 0)
	{
	  SphP[j].Je += SphP[j].Sfr * All.IonizingLumPerSFR *
	    (PROTONMASS / (P[j].Mass * All.UnitMass_in_g / All.HubbleParam)) * All.UnitTime_in_s /
	    All.HubbleParam;
	  printf("ET get_JE ID: %d Proc %d", P[j].ID, ThisTask);
	}
#endif
    }

#ifdef EDDINGTON_TENSOR_STARS

  /* allocate buffers to arrange communication */

  Ngblist = (int *) mymalloc(NumPart * sizeof(int));

  All.BunchSize =
    (int) ((All.BufferSize * 1024 * 1024) / (sizeof(struct data_index) + sizeof(struct data_nodelist) +
					     2 * sizeof(struct stardata_in)));
  DataIndexTable = (struct data_index *) mymalloc(All.BunchSize * sizeof(struct data_index));
  DataNodeList = (struct data_nodelist *) mymalloc(All.BunchSize * sizeof(struct data_nodelist));

  i = FirstActiveParticle;	/* beginn with this index */

  do
    {
      for(j = 0; j < NTask; j++)
	{
	  Send_count[j] = 0;
	  Exportflag[j] = -1;
	}

      /* do local particles and prepare export list */
      for(nexport = 0; i >= 0; i = NextActiveParticle[i])
	{
	  if(P[i].Type == 4)
	    {
	      if(star_density_evaluate(i, 0, &nexport, Send_count) < 0)
		break;
	    }
	}

#ifdef MYSORT
      mysort_dataindex(DataIndexTable, nexport, sizeof(struct data_index), data_index_compare);
#else
      qsort(DataIndexTable, nexport, sizeof(struct data_index), data_index_compare);
#endif

      MPI_Allgather(Send_count, NTask, MPI_INT, Sendcount_matrix, NTask, MPI_INT, MPI_COMM_WORLD);

      for(j = 0, nimport = 0, Recv_offset[0] = 0, Send_offset[0] = 0; j < NTask; j++)
	{
	  Recv_count[j] = Sendcount_matrix[j * NTask + ThisTask];
	  nimport += Recv_count[j];

	  if(j > 0)
	    {
	      Send_offset[j] = Send_offset[j - 1] + Send_count[j - 1];
	      Recv_offset[j] = Recv_offset[j - 1] + Recv_count[j - 1];
	    }
	}

      StarDataGet = (struct stardata_in *) mymalloc(nimport * sizeof(struct stardata_in));
      StarDataIn = (struct stardata_in *) mymalloc(nexport * sizeof(struct stardata_in));

      /* prepare particle data for export */
      for(j = 0; j < nexport; j++)
	{
	  place = DataIndexTable[j].Index;

	  StarDataIn[j].Pos[0] = P[place].Pos[0];
	  StarDataIn[j].Pos[1] = P[place].Pos[1];
	  StarDataIn[j].Pos[2] = P[place].Pos[2];
	  StarDataIn[j].Hsml = PPP[place].Hsml;
	  StarDataIn[j].Density = P[place].DensAroundStar;
	  StarDataIn[j].Mass = P[place].Mass;

	  memcpy(StarDataIn[j].NodeList,
		 DataNodeList[DataIndexTable[j].IndexGet].NodeList, NODELISTLENGTH * sizeof(int));

	}

      /* exchange particle data */
      for(ngrp = 1; ngrp < (1 << PTask); ngrp++)
	{
	  sendTask = ThisTask;
	  recvTask = ThisTask ^ ngrp;

	  if(recvTask < NTask)
	    {
	      if(Send_count[recvTask] > 0 || Recv_count[recvTask] > 0)
		{
		  /* get the particles */
		  MPI_Sendrecv(&StarDataIn[Send_offset[recvTask]],
			       Send_count[recvTask] * sizeof(struct stardata_in), MPI_BYTE,
			       recvTask, TAG_DENS_A,
			       &StarDataGet[Recv_offset[recvTask]],
			       Recv_count[recvTask] * sizeof(struct stardata_in), MPI_BYTE,
			       recvTask, TAG_DENS_A, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
		}
	    }
	}

      myfree(StarDataIn);

      /* now do the particles that were sent to us */

      for(j = 0; j < nimport; j++)
	star_density_evaluate(j, 1, &dummy, &dummy);

      /* check whether this is the last iteration */
      if(i < 0)
	ndone_flag = 1;
      else
	ndone_flag = 0;

      MPI_Allreduce(&ndone_flag, &ndone, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);

      myfree(StarDataGet);
    }
  while(ndone < NTask);

  myfree(DataNodeList);
  myfree(DataIndexTable);
  myfree(Ngblist);

#endif //for EDDINGTON_TENSOR_STARS

}

#ifdef EDDINGTON_TENSOR_STARS
/*! This function represents the core of the SPH density computation. The
 *  target particle may either be local, or reside in the communication
 *  buffer.
 */
int star_density_evaluate(int target, int mode, int *nexport, int *nsend_local)
{
  int j, n, numngb;
  int startnode, listindex = 0;
  double h, hinv, h2, mass_j, weight, hinv3;
  double wk, mass, density, lum;
  double dx, dy, dz, r, r2, u, a3inv;
  MyDouble *pos;

#ifdef PERIODIC
  double boxsize, boxhalf;

  boxsize = All.BoxSize;
  boxhalf = 0.5 * All.BoxSize;
#endif

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

  if(mode == 0)
    {
      pos = P[target].Pos;
      h = PPP[target].Hsml;
      density = P[target].DensAroundStar;
      mass = P[target].Mass;
    }
  else
    {
      pos = StarDataGet[target].Pos;
      h = StarDataGet[target].Hsml;
      density = StarDataGet[target].Density;
      mass = StarDataGet[target].Mass;
    }

  h2 = h * h;
  hinv = 1.0 / h;
  hinv3 = hinv * hinv * hinv;

  lum = mass * All.IonizingLumPerSolarMass * (PROTONMASS / SOLAR_MASS) * All.UnitTime_in_s / All.HubbleParam;

  if(mode == 0)
    {
      startnode = All.MaxPart;	/* root node */
    }
  else
    {
      startnode = StarDataGet[target].NodeList[0];
      startnode = Nodes[startnode].u.d.nextnode;	/* open it */
    }

  while(startnode >= 0)
    {
      while(startnode >= 0)
	{
	  numngb = ngb_treefind_variable(pos, h, target, &startnode, mode, nexport, nsend_local);

	  if(numngb < 0)
	    return -1;

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

	      dx = pos[0] - P[j].Pos[0];
	      dy = pos[1] - P[j].Pos[1];
	      dz = pos[2] - P[j].Pos[2];
#ifdef PERIODIC
	      if(dx > boxHalf_X)
		dx -= boxSize_X;
	      if(dx < -boxHalf_X)
		dx += boxSize_X;
	      if(dy > boxHalf_Y)
		dy -= boxSize_Y;
	      if(dy < -boxHalf_Y)
		dy += boxSize_Y;
	      if(dz > boxHalf_Z)
		dz -= boxSize_Z;
	      if(dz < -boxHalf_Z)
		dz += boxSize_Z;
#endif
	      r2 = dx * dx + dy * dy + dz * dz;
	      r = sqrt(r2);

	      if(r2 < h2)
		{
		  u = r * hinv;

		  if(u < 0.5)
		    wk = hinv3 * (KERNEL_COEFF_1 + KERNEL_COEFF_2 * (u - 1) * u * u);
		  else
		    wk = hinv3 * KERNEL_COEFF_5 * (1.0 - u) * (1.0 - u) * (1.0 - u);

		}
	      else
		wk = 0;

	      mass_j = P[j].Mass;

	      weight = mass_j * wk / density;

	      SphP[j].Je += lum * weight / mass_j;
	    }
	}

      if(mode == 1)
	{
	  listindex++;
	  if(listindex < NODELISTLENGTH)
	    {
	      startnode = StarDataGet[target].NodeList[listindex];
	      if(startnode >= 0)
		startnode = Nodes[startnode].u.d.nextnode;	/* open it */
	    }
	}
    }

  return 0;
}
#endif

#endif


/* The MIT License

   Copyright (c) 2013-2015 Genome Research Ltd.

   Author: Petr Danecek <pd3@sanger.ac.uk>

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.

 */

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_multifit_nlin.h>
#include <htslib/vcf.h>
#include <htslib/synced_bcf_reader.h>
#include "bcftools.h"
#include "peakfit.h"

typedef struct
{
    int nvals;          // all values, including RR,AA peaks
    double *xvals;      // pointer to args_t.xvals
    double *yvals;
    int copy_number;    // heuristics to skip futile CN1 fits when no het peak is detected
    int irr, ira, iaa;  // chop off RR and AA peaks
    char *chr;
}
dist_t;

typedef struct
{
    int ndist, nbins, ra_rr_scaling, smooth;
    double *xvals;
    dist_t *dist;
    char **argv, *output_dir;
    double fit_th, peak_symmetry, cn_penalty, min_peak_size, min_fraction;
    int argc, plot, verbose, regions_is_file, targets_is_file, include_aa, force_cn;
    char *dat_fname, *fname, *regions_list, *targets_list, *sample;
    FILE *dat_fp;
}
args_t;

FILE *open_file(char **fname, const char *mode, const char *fmt, ...);

static void init_dist(args_t *args, dist_t *dist, int verbose)
{
    // isolate RR and AA peaks and rescale so that they are comparable to hets
    int i, irr, iaa, n = dist->nvals;

    // smooth the distribution, this is just to find the peaks
    double *tmp = (double*) malloc(sizeof(double)*n);
    int win  = args->smooth ? fabs(args->smooth)*2 + 1 : 7;   // must be an odd number
    int hwin = win/2;
    double avg = tmp[0] = dist->yvals[0];
    for (i=1; i<hwin; i++)
    {
        avg += dist->yvals[2*i-1];
        tmp[i] = avg/(2*i+1);
    }
    avg = 0;
    for (i=0; i<n; i++)
    {
        avg += dist->yvals[i];
        if ( i>=win-1 )
        {
            tmp[i-hwin] = avg/win;
            avg -= dist->yvals[i-win+1];
        }
    }
    for (i=n-hwin; i<n; i++)
    {
        avg -= dist->yvals[i-hwin];
        hwin--;
        tmp[i] = avg/(2*hwin+1);
        avg -= dist->yvals[i-hwin];
    }

    // find the extremes; first a simple approach: find a gap
    for (irr=0,i=0; i<n/2; i++) if ( tmp[i] < tmp[irr] ) irr = i;
    for (iaa=n-1,i=n-1; i>=n/2; i--) if ( tmp[i] < tmp[iaa] ) iaa = i;
    irr += win*0.5;
    iaa += win*0.5;
    if ( iaa>=n ) iaa = n-1;
    if ( irr>=iaa ) error("FIXME: oops, dist normalization failed for %s: %d vs %d\n", dist->chr,irr,iaa); // we may need to be smarter
    if ( args->smooth>0 ) for (i=0; i<n; i++) dist->yvals[i] = tmp[i];
    free(tmp);

    // clean the data: the AA peak is occasionally not centered at 1.0 but is closer to the center, chop off
    int imax_aa = iaa;
    for (i=iaa; i<n; i++)
        if ( dist->yvals[imax_aa] < dist->yvals[i] ) imax_aa = i;
    dist->nvals = imax_aa+1;
    if ( iaa>=dist->nvals ) iaa = dist->nvals-1;

    // find the maximum and scale the peaks (first draft: no attempt to join the segments smootly)
    double max_rr = 0, max_aa = 0, max_ra = 0, srr = 0, saa = 0, sra = 0;
    for (i=0; i<irr; i++)
    {
        srr += dist->yvals[i];
        if ( max_rr < dist->yvals[i] ) max_rr = dist->yvals[i];
    }
    for (i=irr; i<=iaa; i++)
    {
        sra += dist->yvals[i];
        if ( max_ra < dist->yvals[i] ) max_ra = dist->yvals[i];
    }
    for (i=iaa+1; i<n; i++)
    {
        saa += dist->yvals[i];
        if ( max_aa < dist->yvals[i] ) max_aa = dist->yvals[i];
    }

    // Does the het peak exist at all? Usually the numbers are as follows:
    //  1:  cn=0     ra/rr=0.205730      aa/ra=0.674922      nra=7066
    //  20: cn=0     ra/rr=0.258019      aa/ra=0.548929      nra=2381
    //  X:  cn=0     ra/rr=0.005976      aa/ra=44.116667     nra=60
    //  Y:  cn=0     ra/rr=0.008316      aa/ra=7.250000      nra=12
    //  MT: cn=0     ra/rr=0.013699      aa/ra=0.666667      nra=3

    if ( !args->ra_rr_scaling ) max_ra = max_aa = max_rr;
    if ( !sra || (sra/srr<0.1 && saa/sra>1.0) ) // too few hets, CN1
    {
        max_ra = max_aa;
        dist->copy_number = 1;
    }
    else if ( sra/srr<0.1 || saa/sra>1.0 )
    {
        max_ra = max_aa;
        dist->copy_number = -1;     // unknown copy number
    }
    if ( max_rr ) for (i=0; i<irr; i++) dist->yvals[i] /= max_rr;
    if ( max_ra ) for (i=irr; i<=iaa; i++) dist->yvals[i] /= max_ra;
    if ( max_aa ) for (i=iaa+1; i<n; i++) dist->yvals[i] /= max_aa;

    dist->irr = irr;
    dist->iaa = iaa;
    dist->ira = n*0.5;

    if ( verbose )
        fprintf(stderr,"%s:\t irr,ira,iaa=%.2f,%.2f,%.2f \t cn=%2d \t ra/rr=%f \t aa/ra=%f \t nra=%d\n",
            dist->chr, dist->xvals[irr],dist->xvals[dist->ira],dist->xvals[iaa],
            dist->copy_number,sra/srr,saa/sra, (int)sra);
}

static void init_data(args_t *args)
{
    bcf_srs_t *files = bcf_sr_init();
    if ( args->regions_list )
    {
        if ( bcf_sr_set_regions(files, args->regions_list, args->regions_is_file)<0 )
            error("Failed to read the regions: %s\n", args->regions_list);
    }
    if ( args->targets_list )
    {
        if ( bcf_sr_set_targets(files, args->targets_list, args->targets_is_file, 0)<0 )
            error("Failed to read the targets: %s\n", args->targets_list);
    }
    if ( !bcf_sr_add_reader(files, args->fname) ) error("Failed to open %s: %s\n", args->fname,bcf_sr_strerror(files->errnum));
    bcf_hdr_t *hdr = files->readers[0].header;
    if ( !args->sample )
    {
        if ( bcf_hdr_nsamples(hdr)>1 ) error("Missing the option -s, --sample\n");
        args->sample = hdr->samples[0];
    }
    else if ( bcf_hdr_id2int(hdr,BCF_DT_SAMPLE,args->sample)<0 ) error("No such sample: %s\n", args->sample);
    int ret = bcf_hdr_set_samples(hdr, args->sample, 0);
    if ( ret<0 ) error("Error setting the sample: %s\n", args->sample);

    if ( !bcf_hdr_idinfo_exists(hdr,BCF_HL_FMT,bcf_hdr_id2int(hdr,BCF_DT_ID,"BAF")) )
        error("The tag FORMAT/BAF is not present in the VCF: %s\n", args->fname);

    int i;
    args->xvals = (double*) calloc(args->nbins,sizeof(double));
    for (i=0; i<args->nbins; i++) args->xvals[i] = 1.0*i/(args->nbins-1);

    // collect BAF distributions for all chromosomes
    int idist = -1, nbaf = 0, nprocessed = 0, ntotal = 0, prev_chr = -1;
    float *baf = NULL;
    while ( bcf_sr_next_line(files) )
    {
        ntotal++;

        bcf1_t *line = bcf_sr_get_line(files,0);
        if ( bcf_get_format_float(hdr,line,"BAF",&baf,&nbaf) != 1 ) continue;
        if ( bcf_float_is_missing(baf[0]) ) continue;

        nprocessed++;

        if ( prev_chr==-1 || prev_chr!=line->rid )
        {
            // new chromosome
            idist = args->ndist++;
            args->dist = (dist_t*) realloc(args->dist, sizeof(dist_t)*args->ndist);
            memset(&args->dist[idist],0,sizeof(dist_t));
            args->dist[idist].chr   = strdup(bcf_seqname(hdr,line));
            args->dist[idist].yvals = (double*) calloc(args->nbins,sizeof(double));
            args->dist[idist].xvals = args->xvals;
            args->dist[idist].nvals = args->nbins;
            prev_chr = line->rid;
        }
        int bin = baf[0]*(args->nbins-1);
        args->dist[idist].yvals[bin]++;   // the distribution
    }
    free(baf);
    bcf_sr_destroy(files);

    for (idist=0; idist<args->ndist; idist++)
    {
        #if 0
            int j;
            for (j=0; j<args->nbins; j++)
            {
                double x = args->dist[idist].xvals[j];
                args->dist[idist].yvals[j] = exp(-(x-0.5)*(x-0.5)/1e-3);
            }
        #endif
        init_dist(args, &args->dist[idist],args->verbose);
    }

    args->dat_fp = open_file(&args->dat_fname,"w","%s/dist.dat", args->output_dir);
    fprintf(args->dat_fp, "# This file was produced by: bcftools polysomy(%s+htslib-%s), the command line was:\n", bcftools_version(),hts_version());
    fprintf(args->dat_fp, "# \t bcftools %s ", args->argv[0]);
    for (i=1; i<args->argc; i++)
        fprintf(args->dat_fp, " %s",args->argv[i]);
    fprintf(args->dat_fp,"\n#\n");
    fprintf(args->dat_fp,"# DIST\t[2]Chrom\t[3]BAF\t[4]Normalized Count\n");
    fprintf(args->dat_fp,"# FIT\t[2]Goodness of Fit\t[3]iFrom\t[4]iTo\t[5]The Fitted Function\n");
    fprintf(args->dat_fp,"# CN\t[2]Chrom\t[3]Estimated Copy Number\t[4]Absolute fit deviation\n");

    char *fname = NULL;
    FILE *fp = open_file(&fname,"w","%s/dist.py", args->output_dir);
//-------- matplotlib script --------------
    fprintf(fp,
        "#!/usr/bin/env python\n"
        "#\n"
        "import matplotlib as mpl\n"
        "mpl.use('Agg')\n"
        "import matplotlib.pyplot as plt\n"
        "import csv,sys,argparse\n"
        "from math import exp\n"
        "\n"
        "outdir = '%s'\n"
        "\n"
        "def read_dat(dat,fit,cn):\n"
        "   csv.register_dialect('tab', delimiter='\t', quoting=csv.QUOTE_NONE)\n"
        "   with open(outdir+'/dist.dat', 'rb') as f:\n"
        "      reader = csv.reader(f, 'tab')\n"
        "      for row in reader:\n"
        "          if row[0][0]=='#': continue\n"
        "          type = row[0]\n"
        "          chr  = row[1]\n"
        "          if type=='DIST':\n"
        "              if chr not in dat: dat[chr] = []\n"
        "              dat[chr].append(row)\n"
        "          elif type=='FIT':\n"
        "              if chr not in fit: fit[chr] = []\n"
        "              fit[chr].append(row)\n"
        "          elif type=='CN':\n"
        "              cn[chr] = row[2]\n"
        "\n"
        "def plot_dist(dat,fit,chr):\n"
        "   fig, ax = plt.subplots(1, 1, figsize=(7,5))\n"
        "   ax.plot([x[2] for x in dat[chr]],[x[3] for x in dat[chr]],'k-',label='Distribution')\n"
        "   if chr in fit:\n"
        "       for i in range(len(fit[chr])):\n"
        "           pfit = fit[chr][i]\n"
        "           exec('def xfit(x): return '+pfit[5])\n"
        "           istart = int(pfit[3])\n"
        "           iend   = int(pfit[4])+1\n"
        "           vals   = dat[chr][istart:iend]\n"
        "           args   = {}\n"
        "           if i==0: args = {'label':'Target to Fit'}\n"
        "           ax.plot([x[2] for x in vals],[x[3] for x in vals],'r-',**args)\n"
        "           if i==0: args = {'label':'Best Fit'}\n"
        "           ax.plot([x[2] for x in vals],[xfit(float(x[2])) for x in vals],'g-',**args)\n"
        "   ax.set_title('BAF distribution, chr'+chr)\n"
        "   ax.set_xlabel('BAF')\n"
        "   ax.set_ylabel('Frequency')\n"
        "   ax.legend(loc='best',prop={'size':7},frameon=False)\n"
        "   plt.savefig(outdir+'/dist.chr'+chr+'.png')\n"
        "   plt.close()\n"
        "\n"
        "def plot_copy_number(cn):\n"
        "   fig, ax = plt.subplots(1, 1, figsize=(7,5))\n"
        "   xlabels = sorted(cn.keys())\n"
        "   xvals = range(len(xlabels))\n"
        "   yvals = [float(cn[x]) for x in xlabels]\n"
        "   ax.plot(xvals,yvals,'o',color='red')\n"
        "   for i in range(len(xvals)):\n"
        "       if yvals[i]==-1: ax.annotate('?', xy=(xvals[i],0.5),va='center',ha='center',color='red',fontweight='bold')\n"
        "   ax.tick_params(axis='both', which='major', labelsize=9)\n"
        "   ax.set_xticks(xvals)\n"
        "   ax.set_xticklabels(xlabels,rotation=45)\n"
        "   ax.set_xlim(-1,len(xlabels))\n"
        "   ax.set_ylim(0,5.0)\n"
        "   ax.set_yticks([1.0,2.0,3.0,4.0])\n"
        "   ax.set_xlabel('Chromosome')\n"
        "   ax.set_ylabel('Copy Number')\n"
        "   plt.savefig(outdir+'/copy-number.png')\n"
        "   plt.close()\n"
        "\n"
        "class myParser(argparse.ArgumentParser):\n"
        "   def error(self, message):\n"
        "       self.print_help()\n"
        "       sys.stderr.write('error: %%s\\n' %% message)\n"
        "       sys.exit(2)\n"
        "\n"
        "def main():\n"
        "   parser = myParser()\n"
        "   parser.add_argument('-a', '--all', action='store_true', help='Create all plots')\n"
        "   parser.add_argument('-c', '--copy-number', action='store_true', help='Create copy-number plot')\n"
        "   parser.add_argument('-d', '--distrib', metavar='CHR', help='Plot BAF distribution of a single chromosome')\n"
        "   args = parser.parse_args()\n"
        "   dat = {}; fit = {}; cn = {}\n"
        "   read_dat(dat,fit,cn)\n"
        "   if args.distrib!=None:\n"
        "       plot_dist(dat,fit,args.distrib)\n"
        "   if args.all:\n"
        "       for chr in dat: plot_dist(dat,fit,chr)\n"
        "       plot_copy_number(cn)\n"
        "   elif args.copy_number:\n"
        "       plot_copy_number(cn)\n"
        "   else:\n"
        "       for chr in dat: plot_dist(dat,fit,chr)\n"
        "\n"
        "if __name__ == '__main__':\n"
        "   main()\n",
        args->output_dir);
//---------------------------------------
    chmod(fname, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH|S_IXUSR|S_IXGRP|S_IXOTH);
    free(fname);
    fclose(fp);
}

static void destroy_data(args_t *args)
{
    int i;
    for (i=0; i<args->ndist; i++)
    {
        free(args->dist[i].chr);
        free(args->dist[i].yvals);
    }
    free(args->dist);
    free(args->xvals);
    free(args->dat_fname);
    fclose(args->dat_fp);
}

static void save_dist(args_t *args, dist_t *dist)
{
    int i;
    for (i=0; i<args->nbins; i++)
        fprintf(args->dat_fp,"DIST\t%s\t%f\t%f\n",dist->chr,dist->xvals[i],dist->yvals[i]);
}
static void fit_curves(args_t *args)
{
    peakfit_t *pkf = peakfit_init();
    peakfit_verbose(pkf,args->verbose);

    int i, nmc = 50;
    for (i=0; i<args->ndist; i++)
    {
        dist_t *dist = &args->dist[i];
        save_dist(args, &args->dist[i]);

        if ( dist->copy_number!=0 )
        {
            fprintf(args->dat_fp,"CN\t%s\t%.2f\n", dist->chr,(float)dist->copy_number);
            continue;
        }

        if ( args->verbose )
            fprintf(stderr,"%s:\n", dist->chr);

        int nrr_aa  = dist->iaa - dist->irr + 1;
        int nrr_ra  = dist->ira - dist->irr + 1;
        int naa_max = dist->nvals - dist->iaa;
        double xrr  = dist->xvals[dist->irr], *xrr_vals = &dist->xvals[dist->irr], *yrr_vals = &dist->yvals[dist->irr];
        double xaa  = dist->xvals[dist->iaa], *xaa_vals = &dist->xvals[dist->iaa], *yaa_vals = &dist->yvals[dist->iaa];
        double xra  = dist->xvals[dist->ira];
        double xmax = dist->xvals[dist->nvals-1];

        // CN2
        double cn2aa_fit = 0, cn2ra_fit, cn2_fit;
        char *cn2aa_func = 0, *cn2ra_func;
        double cn2aa_params[3] = {1,1,1} ,cn2ra_params[3];
        if ( args->include_aa )
        {
            peakfit_reset(pkf);
            peakfit_add_exp(pkf, 1.0,1.0,0.2, 5);
            peakfit_set_mc(pkf, 0.01,0.3,2,nmc);
            peakfit_set_mc(pkf, 0.05,1.0,0,nmc);
            cn2aa_fit  = peakfit_run(pkf, naa_max, xaa_vals, yaa_vals);
            cn2aa_func = strdup(peakfit_sprint_func(pkf));
            peakfit_get_params(pkf,0,cn2aa_params,3);
        }
        peakfit_reset(pkf);
        peakfit_add_bounded_gaussian(pkf, 1.0,0.5,0.03, 0.45,0.55, 7);
        peakfit_set_mc(pkf, 0.01,0.3,2,nmc);
        peakfit_set_mc(pkf, 0.05,1.0,0,nmc);
        cn2ra_fit  = peakfit_run(pkf, nrr_aa,xrr_vals,yrr_vals);
        cn2ra_func = strdup(peakfit_sprint_func(pkf));
        cn2_fit    = cn2ra_fit + cn2aa_fit;
        peakfit_get_params(pkf,0,cn2ra_params,3);

        // CN3: fit two peaks, then enforce the symmetry and fit again
        double cn3rra_params[5], cn3raa_params[5], *cn3aa_params = cn2aa_params;
        double cn3aa_fit = cn2aa_fit, cn3ra_fit;
        char *cn3aa_func = cn2aa_func, *cn3ra_func;
        double min_dx3   = 0.5 - 1./(args->min_fraction+2);
        peakfit_reset(pkf);
        peakfit_add_bounded_gaussian(pkf, 1.0,1/3.,0.03, xrr,xra-min_dx3, 7);
        peakfit_set_mc(pkf, xrr,xra-min_dx3, 1,nmc);
        peakfit_add_bounded_gaussian(pkf, 1.0,2/3.,0.03, xra+min_dx3,xaa, 7);
        peakfit_set_mc(pkf, xra+min_dx3,xaa, 1,nmc);
        peakfit_run(pkf, nrr_aa, xrr_vals, yrr_vals);
        // force symmetry around x=0.5
        peakfit_get_params(pkf,0,cn3rra_params,5);
        peakfit_get_params(pkf,1,cn3raa_params,5);
        double cn3_dx = (0.5-cn3rra_params[1] + cn3raa_params[1]-0.5)*0.5;
        if ( cn3_dx > 0.5/3 ) cn3_dx = 0.5/3;   // CN3 peaks should not be separated by more than 1/3
        peakfit_reset(pkf);
        peakfit_add_gaussian(pkf, cn3rra_params[0],0.5-cn3_dx,cn3rra_params[2], 5);
        peakfit_add_gaussian(pkf, cn3raa_params[0],0.5+cn3_dx,cn3raa_params[2], 5);
        cn3ra_fit  = peakfit_run(pkf, nrr_aa, xrr_vals, yrr_vals);
        cn3ra_func = strdup(peakfit_sprint_func(pkf));
        // compare peak sizes
        peakfit_get_params(pkf,0,cn3rra_params,3);
        peakfit_get_params(pkf,1,cn3raa_params,3);
        double cn3rra_size = cn3rra_params[0]*cn3rra_params[0];
        double cn3raa_size = cn3raa_params[0]*cn3raa_params[0];
        double cn3_dy      = cn3rra_size > cn3raa_size ? cn3raa_size/cn3rra_size : cn3rra_size/cn3raa_size;
        double cn3_frac    = (1 - 2*cn3rra_params[1]) / cn3rra_params[1];
        double cn3_fit     = cn3ra_fit + cn3aa_fit;
        // A very reasonable heuristics: check if the peak's width converged, exclude far too broad or far too narrow peaks
        if ( cn3rra_params[2]>0.3  || cn3raa_params[2]>0.3 ) cn3_fit = HUGE_VAL;
        if ( cn3rra_params[2]<1e-2 || cn3raa_params[2]<1e-2 ) cn3_fit = HUGE_VAL;

        // CN4 (contaminations)
        // - first fit only the [0,0.5] part of the data, then enforce the symmetry and fit again
        // - min_frac=1 (resp. 0.5) is interpreted as 50:50% (rep. 75:25%) contamination
        double cn4AAaa_params[3] = {1,1,1} ,cn4AAra_params[3] = {1,1,1}, cn4RAra_params[3], cn4RArr_params[5], cn4RAaa_params[5];
        double cn4aa_fit = 0, cn4ra_fit;
        char *cn4aa_func = 0, *cn4ra_func;
        double min_dx4   = 0.25*args->min_fraction;
        if ( args->include_aa )
        {
            peakfit_reset(pkf);
            peakfit_add_exp(pkf, 0.5,1.0,0.2, 5);
            peakfit_set_mc(pkf, 0.01,0.3,2,nmc);
            peakfit_add_bounded_gaussian(pkf, 0.4,(xaa+xmax)*0.5,2e-2, xaa,xmax, 7);
            peakfit_set_mc(pkf, xaa,xmax, 1,nmc);
            cn4aa_fit  = peakfit_run(pkf, naa_max, xaa_vals,yaa_vals);
            cn4aa_func = strdup(peakfit_sprint_func(pkf));
            peakfit_get_params(pkf,0,cn4AAaa_params,3);
            peakfit_get_params(pkf,1,cn4AAra_params,5);
        }
        peakfit_reset(pkf);
        // first fit only the [0,0.5] part of the data
        peakfit_add_gaussian(pkf, 1.0,0.5,0.03, 5);
        peakfit_add_bounded_gaussian(pkf, 0.6,0.3,0.03, xrr,xra-min_dx4, 7);
        peakfit_set_mc(pkf, xrr,xra-min_dx4,2,nmc);
        peakfit_run(pkf, nrr_ra , xrr_vals, yrr_vals);
        // now forcet symmetry around x=0.5
        peakfit_get_params(pkf,0,cn4RAra_params,3);
        peakfit_get_params(pkf,1,cn4RArr_params,5);
        double cn4_dx = 0.5-cn4RArr_params[1];
        if ( cn4_dx > 0.25 ) cn4_dx = 0.25;   // CN4 peaks should not be separated by more than 0.5
        peakfit_reset(pkf);
        peakfit_add_gaussian(pkf, cn4RAra_params[0],0.5,cn4RAra_params[2], 5);
        peakfit_add_gaussian(pkf, cn4RArr_params[0],0.5-cn4_dx,cn4RArr_params[2], 5);
        peakfit_add_gaussian(pkf, cn4RArr_params[0],0.5+cn4_dx,cn4RArr_params[2], 5);
        peakfit_set_mc(pkf, 0.1,cn4RAra_params[0],0,nmc);
        peakfit_set_mc(pkf, 0.01,0.1,2,nmc);
        cn4ra_fit  = peakfit_run(pkf, nrr_aa , xrr_vals, yrr_vals);
        cn4ra_func = strdup(peakfit_sprint_func(pkf));
        peakfit_get_params(pkf,0,cn4RAra_params,3);
        peakfit_get_params(pkf,1,cn4RArr_params,3);
        peakfit_get_params(pkf,2,cn4RAaa_params,3);
        double cn4RAra_size = cn4RAra_params[0]==0 ? HUGE_VAL : cn4RAra_params[0]*cn4RAra_params[0];
        double cn4RArr_size = cn4RArr_params[0]*cn4RArr_params[0];
        double cn4RAaa_size = cn4RAaa_params[0]*cn4RAaa_params[0];
        double cn4RArr_dy   = cn4RArr_size < cn4RAra_size ? cn4RArr_size/cn4RAra_size : cn4RAra_size/cn4RArr_size;
        double cn4RAaa_dy   = cn4RAaa_size < cn4RAra_size ? cn4RAaa_size/cn4RAra_size : cn4RAra_size/cn4RAaa_size;
        double cn4_dy       = cn4RArr_dy < cn4RAaa_dy ? cn4RArr_dy/cn4RAaa_dy : cn4RAaa_dy/cn4RArr_dy;
        double cn4_ymin     = cn4RArr_size < cn4RAaa_size ? cn4RArr_size/cn4RAra_size : cn4RAaa_size/cn4RAra_size;
        cn4_dx              = (cn4RAaa_params[1]-0.5) - (0.5-cn4RArr_params[1]);
        double cn4_frac     = cn4RAaa_params[1] - cn4RArr_params[1];
        double cn4_fit      = cn4ra_fit + cn4aa_fit;
        // A very reasonable heuristics: check if the peak's width converged, exclude far too broad or far too narrow peaks
        if ( cn4RAra_params[2]>0.3 || cn4RArr_params[2]>0.3 || cn4RAaa_params[2]>0.3 ) cn4_fit = HUGE_VAL;
        if ( cn4RAra_params[2]<1e-2 || cn4RArr_params[2]<1e-2 || cn4RAaa_params[2]<1e-2 ) cn4_fit = HUGE_VAL;

        // Choose the best match
        char cn2_fail = '*', cn3_fail = '*', cn4_fail = '*';
        if ( cn2_fit > args->fit_th ) cn2_fail = 'f';

        if ( cn3_fit > args->fit_th ) cn3_fail = 'f';
        else if ( cn3_dy < args->peak_symmetry ) cn3_fail = 'y';    // size difference is too big

        if ( cn4_fit > args->fit_th ) cn4_fail = 'f';
        else if ( cn4_ymin < args->min_peak_size ) cn4_fail = 'y';      // side peak is too small
        else if ( cn4_dy < args->peak_symmetry ) cn4_fail = 'Y';    // size difference is too big
        else if ( cn4_dx > 0.1 ) cn4_fail = 'x';                    // side peaks placed assymetrically

        double cn = -1, fit = cn2_fit;
        if ( cn2_fail == '*' ) { cn = 2; fit = cn2_fit; }
        if ( cn3_fail == '*' )
        {
            // Use cn_penalty as a tiebreaker. If set to 0.3, cn3_fit must be 30% smaller than cn2_fit.
            if ( cn<0 || cn3_fit < (1-args->cn_penalty) * fit )
            {
                cn = 2 + cn3_frac;
                fit = cn3_fit;
                if ( cn2_fail=='*' ) cn2_fail = 'p';
            }
            else cn3_fail = 'p';
        }
        if ( cn4_fail == '*' )
        {
            if ( cn<0 || cn4_fit < (1-args->cn_penalty) * fit )
            {
                cn = 3 + cn4_frac;
                fit = cn4_fit;
                if ( cn2_fail=='*' ) cn2_fail = 'p';
                if ( cn3_fail=='*' ) cn3_fail = 'p';
            }
            else cn4_fail = 'p';
        }

        if ( args->verbose )
        {
            fprintf(stderr,"\tcn2 %c fit=%e\n", cn2_fail, cn2_fit);
            fprintf(stderr,"\t       .. %e\n", cn2ra_fit);
            fprintf(stderr,"\t            RA:   %f %f %f\n", cn2ra_params[0],cn2ra_params[1],cn2ra_params[2]);
            fprintf(stderr,"\t       .. %e\n", cn2aa_fit);
            fprintf(stderr,"\t            AA:   %f %f %f\n", cn2aa_params[0],cn2aa_params[1],cn2aa_params[2]);
            fprintf(stderr,"\t      func:\n");
            fprintf(stderr,"\t            %s\n", cn2ra_func);
            fprintf(stderr,"\t            %s\n", cn2aa_func);
            fprintf(stderr,"\n");
            fprintf(stderr,"\tcn3 %c fit=%e  frac=%f  symmetry=%f\n", cn3_fail, cn3_fit, cn3_frac, cn3_dy);
            fprintf(stderr,"\t       .. %e\n", cn3ra_fit);
            fprintf(stderr,"\t            RRA:  %f %f %f\n", cn3rra_params[0],cn3rra_params[1],cn3rra_params[2]);
            fprintf(stderr,"\t            RAA:  %f %f %f\n", cn3raa_params[0],cn3raa_params[1],cn3raa_params[2]);
            fprintf(stderr,"\t       .. %e\n", cn3aa_fit);
            fprintf(stderr,"\t            AAA:  %f %f %f\n", cn3aa_params[0],cn3aa_params[1],cn3aa_params[2]);
            fprintf(stderr,"\t      func:\n");
            fprintf(stderr,"\t            %s\n", cn3ra_func);
            fprintf(stderr,"\t            %s\n", cn3aa_func);
            fprintf(stderr,"\n");
            fprintf(stderr,"\tcn4 %c fit=%e  frac=%f  symmetry=%f ymin=%f\n", cn4_fail, cn4_fit, cn4_frac, cn4_dy, cn4_ymin);
            fprintf(stderr,"\t       .. %e\n", cn4ra_fit);
            fprintf(stderr,"\t            RArr:  %f %f %f\n", cn4RArr_params[0],cn4RArr_params[1],cn4RArr_params[2]);
            fprintf(stderr,"\t            RAra:  %f %f %f\n", cn4RAra_params[0],cn4RAra_params[1],cn4RAra_params[2]);
            fprintf(stderr,"\t            RAaa:  %f %f %f\n", cn4RAaa_params[0],cn4RAaa_params[1],cn4RAaa_params[2]);
            fprintf(stderr,"\t       .. %e\n", cn4aa_fit);
            fprintf(stderr,"\t            AAaa:  %f %f %f\n", cn4AAaa_params[0],cn4AAaa_params[1],cn4AAaa_params[2]);
            fprintf(stderr,"\t      func:\n");
            fprintf(stderr,"\t            %s\n", cn4ra_func);
            fprintf(stderr,"\t            %s\n", cn4aa_func);
            fprintf(stderr,"\n");
        }

        if ( args->force_cn==2 || cn2_fail == '*' )
        {
            fprintf(args->dat_fp,"FIT\t%s\t%e\t%d\t%d\t%s\n", dist->chr,cn2ra_fit,dist->irr,dist->iaa,cn2ra_func);
            if ( cn2aa_func ) fprintf(args->dat_fp,"FIT\t%s\t%e\t%d\t%d\t%s\n", dist->chr,cn2aa_fit,dist->iaa,dist->nvals-1,cn2aa_func);
        }
        if ( args->force_cn==3 || cn3_fail == '*' )
        {
            fprintf(args->dat_fp,"FIT\t%s\t%e\t%d\t%d\t%s\n", dist->chr,cn3ra_fit,dist->irr,dist->iaa,cn3ra_func);
            if ( cn3aa_func ) fprintf(args->dat_fp,"FIT\t%s\t%e\t%d\t%d\t%s\n", dist->chr,cn3aa_fit,dist->iaa,dist->nvals-1,cn3aa_func);
        }
        if ( args->force_cn==4 || cn4_fail == '*' )
        {
            fprintf(args->dat_fp,"FIT\t%s\t%e\t%d\t%d\t%s\n", dist->chr,cn4ra_fit,dist->irr,dist->iaa,cn4ra_func);
            if ( cn4aa_func ) fprintf(args->dat_fp,"FIT\t%s\t%e\t%d\t%d\t%s\n", dist->chr,cn4aa_fit,dist->iaa,dist->nvals-1,cn4aa_func);
        }
        fprintf(args->dat_fp,"CN\t%s\t%.2f\t%f\n", dist->chr, cn, fit);

        free(cn2aa_func);
        free(cn2ra_func);
        free(cn3ra_func);
        free(cn4ra_func);
        free(cn4aa_func);
    }

    peakfit_destroy(pkf);
}

static void usage(args_t *args)
{
    fprintf(stderr, "\n");
    fprintf(stderr, "About:   Detect number of chromosomal copies from Illumina's B-allele frequency (BAF)\n");
    fprintf(stderr, "Usage:   bcftools polysomy [OPTIONS] <file.vcf>\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "General options:\n");
    fprintf(stderr, "    -o, --output-dir <path>        \n");
    fprintf(stderr, "    -r, --regions <region>         restrict to comma-separated list of regions\n");
    fprintf(stderr, "    -R, --regions-file <file>      restrict to regions listed in a file\n");
    fprintf(stderr, "    -s, --sample <name>            sample to analyze\n");
    fprintf(stderr, "    -t, --targets <region>         similar to -r but streams rather than index-jumps\n");
    fprintf(stderr, "    -T, --targets-file <file>      similar to -R but streams rather than index-jumps\n");
    fprintf(stderr, "    -v, --verbose                  \n");
    fprintf(stderr, "\n");
    fprintf(stderr, "Algorithm options:\n");
    fprintf(stderr, "    -b, --peak-size <float>        minimum peak size (0-1, larger is stricter) [0.1]\n");
    fprintf(stderr, "    -c, --cn-penalty <float>       penalty for increasing CN (0-1, larger is stricter) [0.7]\n");
    fprintf(stderr, "    -f, --fit-th <float>           goodness of fit threshold (>0, smaller is stricter) [3.3]\n");
    fprintf(stderr, "    -i, --include-aa               include the AA peak in CN2 and CN3 evaluation\n");
    fprintf(stderr, "    -m, --min-fraction <float>     minimum distinguishable fraction of aberrant cells [0.1]\n");
    fprintf(stderr, "    -p, --peak-symmetry <float>    peak symmetry threshold (0-1, larger is stricter) [0.5]\n");
    fprintf(stderr, "\n");
    exit(1);
}

int main_polysomy(int argc, char *argv[])
{
    args_t *args = (args_t*) calloc(1,sizeof(args_t));
    args->argc   = argc; args->argv = argv;
    args->nbins  = 150;
    args->fit_th = 3.3;
    args->cn_penalty = 0.7;
    args->peak_symmetry = 0.5;
    args->min_peak_size = 0.1;
    args->ra_rr_scaling = 1;
    args->min_fraction = 0.1;
    args->smooth = -3;

    static struct option loptions[] =
    {
        {"ra-rr-scaling",0,0,1},    // hidden option
        {"force-cn",1,0,2},         // hidden option
        {"smooth",1,0,'S'},         // hidden option
        {"nbins",1,0,'n'},          // hidden option
        {"include-aa",0,0,'i'},
        {"peak-size",1,0,'b'},
        {"min-fraction",1,0,'m'},
        {"verbose",0,0,'v'},
        {"fit-th",1,0,'f'},
        {"cn-penalty",1,0,'c'},
        {"peak-symmetry",1,0,'p'},
        {"output-dir",1,0,'o'},
        {"sample",1,0,'s'},
        {"targets",1,0,'t'},
        {"targets-file",1,0,'T'},
        {"regions",1,0,'r'},
        {"regions-file",1,0,'R'},
        {0,0,0,0}
    };
    char c, *tmp;
    while ((c = getopt_long(argc, argv, "h?o:vt:T:r:R:s:f:p:c:im:b:n:S:",loptions,NULL)) >= 0)
    {
        switch (c)
        {
            case  1 : args->ra_rr_scaling = 0; break;
            case  2 : args->force_cn = atoi(optarg); break;
            case 'n': args->nbins = atoi(optarg); break;
            case 'S': args->smooth = atoi(optarg); break;
            case 'i': args->include_aa = 1; break;
            case 'b':
                args->min_peak_size = strtod(optarg,&tmp);
                if ( *tmp ) error("Could not parse: -b %s\n", optarg);
                if ( args->min_peak_size<0 || args->min_peak_size>1 ) error("Range error: -b %s\n", optarg);
                break;
            case 'm':
                args->min_fraction = strtod(optarg,&tmp);
                if ( *tmp ) error("Could not parse: -n %s\n", optarg);
                if ( args->min_fraction<0 || args->min_fraction>1 ) error("Range error: -n %s\n", optarg);
                break;
            case 'f':
                args->fit_th = strtod(optarg,&tmp);
                if ( *tmp ) error("Could not parse: -f %s\n", optarg);
                break;
            case 'p':
                args->peak_symmetry = strtod(optarg,&tmp);
                if ( *tmp ) error("Could not parse: -p %s\n", optarg);
                break;
            case 'c':
                args->cn_penalty = strtod(optarg,&tmp);
                if ( *tmp ) error("Could not parse: -c %s\n", optarg);
                break;
            case 's': args->sample = optarg; break;
            case 't': args->targets_list = optarg; break;
            case 'T': args->targets_list = optarg; args->targets_is_file = 1; break;
            case 'r': args->regions_list = optarg; break;
            case 'R': args->regions_list = optarg; args->regions_is_file = 1; break;
            case 'o': args->output_dir = optarg; break;
            case 'v': args->verbose++; break;
            default: usage(args); break;
        }
    }
    if ( optind>=argc )
    {
        if ( !isatty(fileno((FILE *)stdin)) ) args->fname = "-";
    }
    else args->fname = argv[optind];
    if ( !args->fname ) usage(args);
    if ( !args->output_dir ) error("Missing the -o option\n");

    init_data(args);
    fit_curves(args);
    destroy_data(args);
    free(args);

    return 0;
}


/* Copyright (C) 2015 Atsushi Togo */
/* All rights reserved. */

/* This file is part of phonopy. */

/* Redistribution and use in source and binary forms, with or without */
/* modification, are permitted provided that the following conditions */
/* are met: */

/* * Redistributions of source code must retain the above copyright */
/*   notice, this list of conditions and the following disclaimer. */

/* * Redistributions in binary form must reproduce the above copyright */
/*   notice, this list of conditions and the following disclaimer in */
/*   the documentation and/or other materials provided with the */
/*   distribution. */

/* * Neither the name of the phonopy project nor the names of its */
/*   contributors may be used to endorse or promote products derived */
/*   from this software without specific prior written permission. */

/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE */
/* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, */
/* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; */
/* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER */
/* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT */
/* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN */
/* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */
/* POSSIBILITY OF SUCH DAMAGE. */

#ifndef __frequency_shift4_H__
#define __frequency_shift4_H__

#include <lapacke.h>
#include <phonoc_array.h>

void get_fc4_frequency_shifts(double *frequency_shifts,
			      const double *fc4_normal_real,
			      const double *frequencies,
			      const Iarray *grid_points1,
			      const Darray *temperatures,
			      const int *band_indicies,
			      const int num_band0,
			      const int num_band,
			      const double unit_conversion_factor);
void
get_fc4_normal_for_frequency_shift(double *fc4_normal_real,
				   const double *frequencies,
				   const lapack_complex_double *eigenvectors,
				   const int grid_point0,
				   const Iarray *grid_points1,
				   const int *grid_address,
				   const int *mesh,
				   const double *fc4,
				   const Darray *shortest_vectors,
				   const Iarray *multiplicity,
				   const double *masses,
				   const int *p2s_map,
				   const int *s2p_map,
				   const Iarray *band_indicies,
				   const double cutoff_frequency);
void reciprocal_to_normal4(lapack_complex_double *fc4_normal,
			   const lapack_complex_double *fc4_reciprocal,
			   const double *freqs0,
			   const double *freqs1,
			   const lapack_complex_double *eigvecs0,
			   const lapack_complex_double *eigvecs1,
			   const double *masses,
			   const int *band_indices,
			   const int num_band0,
			   const int num_band,
			   const double cutoff_frequency);
void set_phonons_for_frequency_shift(Darray *frequencies,
				     Carray *eigenvectors,
				     char *phonon_done,
				     const Iarray *grid_points,
				     const int *grid_address,
				     const int *mesh,
				     const Darray *fc2,
				     const Darray *svecs_fc2,
				     const Iarray *multi_fc2,
				     const double *masses_fc2,
				     const int *p2s_fc2,
				     const int *s2p_fc2,
				     const double unit_conversion_factor,
				     const double *born,
				     const double *dielectric,
				     const double *reciprocal_lattice,
				     const double *q_direction,
				     const double nac_factor,
				     const char uplo);

#endif


/**
 *
 * @file qwrapper_clantr.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
 * @generated c Tue Jan  7 11:44:57 2014
 *
 **/
#include <lapacke.h>
#include "common.h"

void
CORE_clantr_quark(Quark *quark);
void
CORE_clantr_f1_quark(Quark *quark);

/***************************************************************************//**
 *
 **/
void QUARK_CORE_clantr(Quark *quark, Quark_Task_Flags *task_flags,
                       PLASMA_enum norm, PLASMA_enum uplo, PLASMA_enum diag, int M, int N,
                       const PLASMA_Complex32_t *A, int LDA, int szeA,
                       int szeW, float *result)
{
    szeW = max(1, szeW);
    DAG_CORE_LANGE;
    QUARK_Insert_Task(quark, CORE_clantr_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_Complex32_t)*szeA,     A,             INPUT,
        sizeof(int),                        &LDA,   VALUE,
        sizeof(float)*szeW,                 NULL,          SCRATCH,
        sizeof(float),                      result,        OUTPUT,
        0);
}

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

    if ( result == fake ) {
        QUARK_Insert_Task(quark, CORE_clantr_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_Complex32_t)*szeA,     A,             INPUT,
            sizeof(int),                        &LDA,   VALUE,
            sizeof(float)*szeW,                 NULL,          SCRATCH,
            sizeof(float),                      result,        OUTPUT | GATHERV,
            0);
    } else {
        QUARK_Insert_Task(quark, CORE_clantr_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_Complex32_t)*szeA,     A,             INPUT,
            sizeof(int),                        &LDA,   VALUE,
            sizeof(float)*szeW,                 NULL,          SCRATCH,
            sizeof(float),                      result,        OUTPUT,
            sizeof(float)*szeF,                 fake,          OUTPUT | GATHERV,
            0);
    }
}

/***************************************************************************//**
 *
 **/
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_clantr_quark = PCORE_clantr_quark
#define CORE_clantr_quark PCORE_clantr_quark
#endif
void CORE_clantr_quark(Quark *quark)
{
    float *normA;
    PLASMA_enum norm, uplo, diag;
    int M;
    int N;
    PLASMA_Complex32_t *A;
    int LDA;
    float *work;

    quark_unpack_args_9(quark, norm, uplo, diag, M, N, A, LDA, work, normA);
    *normA = LAPACKE_clantr_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_clantr_f1_quark = PCORE_clantr_f1_quark
#define CORE_clantr_f1_quark PCORE_clantr_f1_quark
#endif
void CORE_clantr_f1_quark(Quark *quark)
{
    float *normA;
    PLASMA_enum norm, uplo, diag;
    int M;
    int N;
    PLASMA_Complex32_t *A;
    int LDA;
    float *work;
    float *fake;

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


#ifndef SYSTEM_H_
#define SYSTEM_H_

#pragma once

//#include <gsl/gsl_matrix.h>
#include <fstream>
//#include <gsl/gsl_vector.h>
//#include <gsl/gsl_math.h>
//#include <gsl/gsl_multimin.h>
//#include <gsl/gsl_errno.h>

#include <memory>
#include <math.h>
#include <thrust/extrema.h>
#include <thrust/sort.h>
#include <thrust/copy.h>
#include <thrust/for_each.h>
#include <thrust/transform.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/tuple.h>
#include <thrust/device_vector.h>
#include <thrust/device_ptr.h>
#include <thrust/remove.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/copy.h>
#include <thrust/execution_policy.h>
#include <thrust/pair.h>
#include <stdint.h>

//
struct CapsidInfoVecs {
	thrust::device_vector<int> id_bucket;	//bucket id
	// bucket value means global rank of a certain point
	thrust::device_vector<int> id_value;//node id

 	thrust::device_vector<double> nodeLocX;
	thrust::device_vector<double> nodeLocY;
	thrust::device_vector<double> nodeLocZ;

	//used for capside linear springs binding to membrane
	thrust::device_vector<int> tempMembraneId;
	thrust::device_vector<int> tempCapsideId;

	thrust::device_vector<double> tempLengthsPairs;
	thrust::device_vector<double> tempNodeForceX;
	thrust::device_vector<double> tempNodeForceY;
	thrust::device_vector<double> tempNodeForceZ;

	//used for capside repulsion
	int factor = 10;//number of default nodes repulsion can interact with.
	thrust::device_vector<int> tempNodeIdUnreduced;
	thrust::device_vector<double> tempNodeForceXUnreduced;
	thrust::device_vector<double> tempNodeForceYUnreduced;
	thrust::device_vector<double> tempNodeForceZUnreduced;

	thrust::device_vector<int> tempNodeIdReduced;
	thrust::device_vector<double> tempNodeForceXReduced;
	thrust::device_vector<double> tempNodeForceYReduced;
	thrust::device_vector<double> tempNodeForceZReduced;

	double spring_constant = 10.0;
	double length_zero = 0.97;
	double length_cutoff = 1.63;
	int maxNodeCount;
	double viscosity = 1.0;
	double forceX = 0.0;
	double forceY = 0.0;
	double forceZ = 0.0;

	int num_connections=0;

};

//Data Structure for node location. velocity and force
struct CoordInfoVecs {

	thrust::device_vector<double> scaling_per_edge;

	thrust::device_vector<int> nodes2Triangles_1;
	thrust::device_vector<int> nodes2Triangles_2;
	thrust::device_vector<int> nodes2Triangles_3;
	thrust::device_vector<int> nodes2Triangles_4;
	thrust::device_vector<int> nodes2Triangles_5;
	thrust::device_vector<int> nodes2Triangles_6;
	thrust::device_vector<int> nodes2Triangles_7;
	thrust::device_vector<int> nodes2Triangles_8;
	thrust::device_vector<int> nodes2Triangles_9;
//	thrust::device_vector<int> nodes2Triangles_10;
//	thrust::device_vector<int> nodes2Triangles_11;
//	thrust::device_vector<int> nodes2Triangles_12;

	thrust::device_vector<double> SurfaceNormalX;
	thrust::device_vector<double> SurfaceNormalY;
	thrust::device_vector<double> SurfaceNormalZ;

	thrust::device_vector<int> nndata1;
	thrust::device_vector<int> nndata2;
	thrust::device_vector<int> nndata3;
	thrust::device_vector<int> nndata4;
	thrust::device_vector<int> nndata5;
	thrust::device_vector<int> nndata6;
	thrust::device_vector<int> nndata7;
	thrust::device_vector<int> nndata8;
	thrust::device_vector<int> nndata9;
//	thrust::device_vector<int> nndata10;
//	thrust::device_vector<int> nndata11;
//	thrust::device_vector<int> nndata12;

	thrust::device_vector<bool> isNodeFixed;
	//GLOBAL COORDS
	// X,Y,Z, location, velocity and force of all nodes
	thrust::device_vector<double> prevNodeLocX;
	thrust::device_vector<double> prevNodeLocY;
	thrust::device_vector<double> prevNodeLocZ;

	thrust::device_vector<double> prevNodeForceX;
	thrust::device_vector<double> prevNodeForceY;
	thrust::device_vector<double> prevNodeForceZ;

 	thrust::device_vector<double> nodeLocX;
	thrust::device_vector<double> nodeLocY;
	thrust::device_vector<double> nodeLocZ;

	thrust::device_vector<double> nodeForceX;
	thrust::device_vector<double> nodeForceY;
	thrust::device_vector<double> nodeForceZ;

	thrust::device_vector<double> tempNodeForceXReduced;
	thrust::device_vector<double> tempNodeForceYReduced;
	thrust::device_vector<double> tempNodeForceZReduced;
	thrust::device_vector<double> tempNodeForceXUnreduced;
	thrust::device_vector<double> tempNodeForceYUnreduced;
	thrust::device_vector<double> tempNodeForceZUnreduced;

	//LOCAL COORDS
	//indices of each triangle
	int num_triangles;
	thrust::device_vector<int> triangles2Nodes_1;
	thrust::device_vector<int> triangles2Nodes_2;
	thrust::device_vector<int> triangles2Nodes_3;

	//indices of each edge
	int num_edges;
	thrust::device_vector<int> edges2Nodes_1;
	thrust::device_vector<int> edges2Nodes_2;

	//indices of 2 triangle on each edge
	thrust::device_vector<int> edges2Triangles_1;
	thrust::device_vector<int> edges2Triangles_2;

	//indices of edges on each triangle.
	thrust::device_vector<int> triangles2Edges_1;
	thrust::device_vector<int> triangles2Edges_2;
	thrust::device_vector<int> triangles2Edges_3;

};

//struct used for linking of nodes in network
struct AuxVecs {
	// bucket key means which bucket ID does a certain point fit into
	thrust::device_vector<int> id_bucket;	//bucket id
	// bucket value means global rank of a certain point
	thrust::device_vector<int> id_value;//node id
	// bucket key expanded means what are the bucket IDs are the neighbors of a certain point
	thrust::device_vector<int> id_bucket_expanded;
	// bucket value expanded means each point ( represented by its global rank) will have multiple copies
	thrust::device_vector<int> id_value_expanded;

	// begin position of a keys in id_bucket_expanded and id_value_expanded
	//entry keyBegin[bucketKey] returns start of indices to link
	thrust::device_vector<int> keyBegin;
	// end position of a keys in id_bucket_expanded and id_value_expanded
	thrust::device_vector<int> keyEnd;

	int endIndexid_bucket;
};

struct DomainParams {
	double minX;
	double maxX;
	double minY;
	double maxY;
	double minZ;
	double maxZ;
	double originMinX;
	double originMaxX;
	double originMinY;
	double originMaxY;
	double originMinZ;
	double originMaxZ;
	double gridSpacing = 1.5;//bucket scheme search distance, must be larger than cutoff for capsid
	int XBucketCount;
	int YBucketCount;
	int ZBucketCount;
	int totalBucketCount = 0;
};

struct LJInfoVecs{
	double LJ_PosX;
	double LJ_PosY;
	double LJ_PosZ;
	thrust::device_vector<double> LJ_PosX_all;
	thrust::device_vector<double> LJ_PosY_all;
	thrust::device_vector<double> LJ_PosZ_all;

	double Rmin_M=0.97;//1.0;
	double Rcutoff_M=0.97;//1.0;
	double Rmin_LJ;
	double Rcutoff_LJ;

	double epsilon_M=1.0;
	double epsilon_M_att1;
	double epsilon_M_att2;
	double epsilon_M_rep1;
	double epsilon_M_rep2;
	double epsilon_LJ;
	double epsilon_LJ_rep1;
	double epsilon_LJ_rep2;
	double spring_constant;

	thrust::device_vector<int> node_id_close;
	double lj_energy_M;
	double lj_energy_LJ;
	double forceX;
	double forceY;
	double forceZ;
	thrust::device_vector<double> forceX_all;
	thrust::device_vector<double> forceY_all;
	thrust::device_vector<double> forceZ_all;

};

struct AreaTriangleInfoVecs {

	int factor = 3;//used for reduction
	double initial_area = 0.0048013;
	double spring_constant;
	double spring_constant_weak;

	double area_triangle_energy;

	thrust::device_vector<int> tempNodeIdUnreduced;
	thrust::device_vector<double> tempNodeForceXUnreduced;
	thrust::device_vector<double> tempNodeForceYUnreduced;
	thrust::device_vector<double> tempNodeForceZUnreduced;

	thrust::device_vector<int> tempNodeIdReduced;
	thrust::device_vector<double> tempNodeForceXReduced;
	thrust::device_vector<double> tempNodeForceYReduced;
	thrust::device_vector<double> tempNodeForceZReduced;

};

struct BendingTriangleInfoVecs {
	int numBendingSprings=0;

	int factor = 4;//used for reduction
	double spring_constant;
	double spring_constant_weak;
	double spring_constant_raft;
	double spring_constant_coat;
	double initial_angle = 0.0;//radians
	double initial_angle_raft;
	double initial_angle_coat;

	double bending_triangle_energy;

	thrust::device_vector<int> tempNodeIdUnreduced;
	thrust::device_vector<double> tempNodeForceXUnreduced;
	thrust::device_vector<double> tempNodeForceYUnreduced;
	thrust::device_vector<double> tempNodeForceZUnreduced;

	thrust::device_vector<int> tempNodeIdReduced;
	thrust::device_vector<double> tempNodeForceXReduced;
	thrust::device_vector<double> tempNodeForceYReduced;
	thrust::device_vector<double> tempNodeForceZReduced;
};
struct LinearSpringInfoVecs {

	int factor = 2;//used for reduction
	double spring_constant;
	double spring_constant_weak;
	double spring_constant_att1;
	double spring_constant_att2;
	double spring_constant_rep1; //This is the "D" in Morse potential
	double spring_constant_rep2; //This is the "a" in Morse potential

	double linear_spring_energy;
	double memrepulsion_energy;
	double scalar_edge_length;

	thrust::device_vector<double> edge_initial_length;

	thrust::device_vector<int> tempNodeIdUnreduced;
	thrust::device_vector<double> tempNodeForceXUnreduced;
	thrust::device_vector<double> tempNodeForceYUnreduced;
	thrust::device_vector<double> tempNodeForceZUnreduced;

	thrust::device_vector<int> tempNodeIdReduced;
	thrust::device_vector<double> tempNodeForceXReduced;
	thrust::device_vector<double> tempNodeForceYReduced;
	thrust::device_vector<double> tempNodeForceZReduced;
};

struct GeneralParams{
	double kT;
	double kT_growth;
	double tau;
	int solve_time=100;
	double Rmin = 1.0; //Current mesh minimum edge length, subject to change.
	double abs_Rmin;
	int iteration = 0;
	int maxNodeCount;
	int maxNodeCountLJ;
	double length_scale;
	//parameters for advancing timestep and determining equilibrium

	double dt;
	double nodeMass = 1.0;
	int edges_in_upperhem_list_length;
	double growth_energy_scaling;
	thrust::device_vector<int> edge_to_ljparticle;
	thrust::device_vector<int> nodes_in_upperhem;
	thrust::device_vector<int> edges_in_upperhem;
	thrust::device_vector<int> edges_in_upperhem_list;
	thrust::device_vector<int> triangles_in_upperhem;
	thrust::device_vector<int> boundaries_in_upperhem;
	thrust::device_vector<double> angle_per_edge;
	double centerX = 0.0;
	double centerY = 0.0;
	double centerZ = 0.0;

	double current_total_volume;
	double true_current_total_volume;
	double eq_total_volume;
	double volume_spring_constant;
	double volume_energy;
	double eq_total_boundary_length;
	double line_tension_energy;
	double line_tension_constant;
	double safeguardthreshold;

	//int num_of_triangles;
	//int num_of_edges;
	int true_num_edges;

	double insertion_energy_cost;
	double strain_threshold;

	int SCALE_TYPE;
	double scaling_pow;
	double gausssigma;
	double hilleqnconst;
	double hilleqnpow;

	thrust::device_vector<int> no_weakening;
	double septin_ring_z;
	double boundary_z;

};

class Storage;
class SystemBuilder;
struct HostSetInfoVecs;

class System {
public:
	std::weak_ptr<SystemBuilder> weak_bld_ptr;
	GeneralParams generalParams;
	DomainParams domainParams;
	AuxVecs auxVecs;
	CoordInfoVecs coordInfoVecs;

	CapsidInfoVecs capsidInfoVecs;
	LinearSpringInfoVecs linearSpringInfoVecs;
	BendingTriangleInfoVecs bendingTriangleInfoVecs;
	AreaTriangleInfoVecs areaTriangleInfoVecs;
	LJInfoVecs ljInfoVecs;

	std::shared_ptr<Storage> storage;

	//gsl_vector* df;
	//gsl_vector* locations;

public:

	System();

	void set_weak_builder(std::weak_ptr<SystemBuilder> _weak_bld_ptr);

	void PrintForce();

	void initializeSystem(HostSetInfoVecs& hostSetInfoVecs);

	void assignStorage(std::shared_ptr<Storage> _storage);

	void solveSystem();

	void setExtras();

	void setBucketScheme();

	//void Solve_Forces(const gsl_vector* temp_locations);
	void Solve_Forces();
	//double Solve_Energy(const gsl_vector* temp_locations);

	//void dev_to_gsl_loc_update(gsl_vector* temp_locations);
	//void gsl_to_dev_loc_update(const gsl_vector* temp_locations);

};

#endif /*POLYMERSYSTEM_H_*/


#include <config.h>
#include <stddef.h>
#include <stdlib.h>
#include <math.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_complex.h>

#include <gsl/gsl_fft_halfcomplex.h>
#include <gsl/gsl_fft_halfcomplex_float.h>

#define BASE_DOUBLE
#include "templates_on.h"
#include "hc_pass.h"
#include "hc_init.c"
#include "hc_main.c"
#include "hc_pass_2.c"
#include "hc_pass_3.c"
#include "hc_pass_4.c"
#include "hc_pass_5.c"
#include "hc_pass_n.c"
#include "hc_radix2.c"
#include "hc_unpack.c"
#include "templates_off.h"
#undef  BASE_DOUBLE

#define BASE_FLOAT
#include "templates_on.h"
#include "hc_pass.h"
#include "hc_init.c"
#include "hc_main.c"
#include "hc_pass_2.c"
#include "hc_pass_3.c"
#include "hc_pass_4.c"
#include "hc_pass_5.c"
#include "hc_pass_n.c"
#include "hc_radix2.c"
#include "hc_unpack.c"
#include "templates_off.h"
#undef  BASE_FLOAT


/*
 * Copyright (c) 2016-2021 lymastee, All rights reserved.
 * Contact: lymastee@hotmail.com
 *
 * This file is part of the gslib project.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#pragma once

#ifndef rectpack_89b58399_0f97_4cb9_9160_0226504762b1_h
#define rectpack_89b58399_0f97_4cb9_9160_0226504762b1_h

#include <ariel/config.h>
#include <gslib/bintree.h>
#include <gslib/std.h>
#include <gslib/math.h>
#include <ariel/type.h>

__ariel_begin__

struct rp_rect
{
    float               x, y;                           /* left, top */
    float               width, height;

public:
    rp_rect() { x = y = width = height = 0.f; }
    rp_rect(float l, float t, float w, float h) { set_rect(l, t, w, h); }
    void set_rect(float l, float t, float w, float h) { x = l, y = t, width = w, height = h; }
    float left() const { return x; }
    float top() const { return y; }
    float right() const { return x + width; }
    float bottom() const { return y + height; }
    void move_to(float destx, float desty) { x = destx, y = desty; }
    void offset(float cx, float cy) { x += cx, y += cy; }
    float area() const { return width * height; }
};

struct rp_node:
    public rp_rect
{
    bool                transposed;
    void*               bind_ptr;

public:
    rp_node()
    {
        transposed = false;
        bind_ptr = nullptr;
    }
    virtual ~rp_node() {}
    virtual bool is_dynamic() const { return false; }
    virtual void set_transposed(bool b) { transposed = b; }
    virtual string to_string() const { return string(); }   /* for debug only */
    void set_bind_ptr(void* p) { bind_ptr = p; }
    bool is_empty() const { return !bind_ptr; }
};

struct rp_dynamic_node:
    public rp_node
{
    rp_rect             blank;
    bool                has_blank;                      /* tag of blank.                | only valid on a non-leaf node. */
    bool                is_vert_arrange;                /* vertical arrangement?        | only valid on a non-leaf node. */

public:
    rp_dynamic_node() { has_blank = false; }
    virtual bool is_dynamic() const override { return true; }
    virtual void set_transposed(bool b) override;
    virtual string to_string() const override;
    float get_area() const;
    void set_no_blank();
    void set_blank(const rp_rect& rc);
    void set_from_local(rp_dynamic_node& p);
    void set_dimensions(float w, float h);
    void move_to_pos(float x, float y);
    void flip_leaf();
    void flip_non_leaf();
};

typedef _bintreenode_wrapper<rp_node> rp_wrapper;
typedef _bintree_allocator<rp_wrapper> rp_allocator;
typedef bintree<rp_node, rp_wrapper, rp_allocator> rp_tree;
typedef rp_tree::iterator rp_iterator;
typedef rp_tree::const_iterator rp_const_iterator;

struct rp_input
{
    float               width, height;
    void*               binding;
};

typedef list<rp_input> rp_input_list;

extern void rp_global_location(rp_const_iterator i, rp_rect& rc);
extern bool rp_is_node_transposed(rp_const_iterator i);

class rect_packer
{
public:
    enum packing_strategy
    {
        ps_unknown,
        ps_compactly,
        ps_dynamically,
    };

public:
    rect_packer();
    ~rect_packer();
    const rp_node& get_root_node() const { return *_tree.const_root(); }
    bool is_empty() const { return !_tree.is_valid(); }
    float get_width() const { return _tree.const_root()->width; }
    float get_height() const { return _tree.const_root()->height; }
    int get_pack_times() const { return _pack_times; }
    void pack_automatically(rp_input_list& inputs);
    void tracing() const;
    void trace_total() const;
    void trace_blank() const;
    void trace_tree() const;
    bool checkups() const;

public:
    template<class _lamb>
    void for_each(rp_iterator i, _lamb fn)
    {
        assert(i);
        _tree.preorder_traversal([&fn](rp_wrapper* w) {
            assert(w);
            rp_iterator i(w);
            if(i.is_leaf() && !i->is_empty()) {
                rp_rect rc;
                rp_global_location(i, rc);
                fn(i->bind_ptr, rc, i->transposed);
            }
        });
    }
    template<class _lamb>
    void for_each(_lamb fn)
    {
        if(!_tree.is_valid())
            return;
        for_each(_tree.get_root(), fn);
    }

protected:
    rp_tree             _tree;
    packing_strategy    _strategy;
    int                 _pack_times;

protected:
    void initialize_compactly(float w, float h);
    bool pack_compactly(const rp_input_list& inputs);
    bool add_rect_compactly(float w, float h, void* binding);
    void trace_compactly() const;
    void initialize_dynamically();
    void add_rect_dynamically(float w, float h, void* binding);
    void add_rect_dynamically(rp_tree& rc);
    bool add_rect_dynamically(rp_tree& rc, rp_tree& xchg);
    void proc_fill_up(rp_iterator p, rp_tree& t);
    void proc_combine(rp_iterator p, rp_tree& t);
    void proc_combine_root(rp_iterator p, rp_tree& t);
    void proc_combine_non_root(rp_iterator p, rp_iterator i, rp_tree& t);
    void proc_replace(rp_iterator p, rp_tree& t, rp_tree& xchg);
    void trace_dynamically() const;
};

__ariel_end__

#endif


#ifndef _SPC_RESP_H

#define _SPC_RESP_H

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

#include <gsl/gsl_spline.h>
#include <gsl/gsl_nan.h>
#include <gsl/gsl_sys.h>

#include "fitsio.h"
#include "aXe_grism.h"
#include "spc_cfg.h"
#include "spc_spc.h"

#define RESPBUFFERSIZE 1024

// number of points used
// for smoothing
#define RESP_SMOOTH_LENGTH 50

// the number of sigmas
// to smooth over
#define RESP_SMOOTH_NSIG 3.0

#define MAX_ITER_TL 1000

/*
 * Struct: interpolator
 */
typedef struct
{
  double xmin;
  double xmax;
  interpolator *resp_values;
  interpolator *resp_errors;
}
  response_function;

typedef struct
{
  double wavelength;
  const calib_function *wl_calibration;
}
trlength_search;

extern spectrum *
get_response_function_from_FITS(char filename[], int hdunum);

extern void
apply_response_function(spectrum *spec, spectrum *resp, const int quant_cont);

extern void
apply_response_functionII(spectrum *spec, response_function *resp_func, const int quant_cont);

extern d_point
get_smooth_pars_for_beam(const aperture_conf *conf, const int smooth_conv, beam actbeam);

extern int
check_conf_for_smoothing(const aperture_conf *conf, const int smooth_conv);

extern void
apply_smoothed_response(const calib_function *wl_calibration, const int for_grism,
                        const int quant_cont, response_function *resp_func,
                        const d_point smooth_pars, spectrum *spec);

extern double
find_wavelength(double x, void *params);

extern double
get_tlength_prismwav(const double wavelength, const calib_function *wl_calibration);

extern double
get_central_tracelength(const double wavelength, const calib_function *wl_calibration,
                        const int for_grism);

extern void
get_smoothed_response(const double wavelength, const d_point smooth_pars,
                           const calib_function *wl_calibration, const int for_grism,
                           const gsl_vector *weights, response_function *resp_func,
                           double *resp_vals);
//extern void
//get_smoothed_response(const double wavelength, const double sigma_wav, const gsl_vector *weights,
//                     response_function *resp_func, double *resp_vals);

extern void
get_weighted_sensitivity(const gsl_vector *pixvalues, const gsl_vector *errvalues, const gsl_vector *weights,
                         const gsl_vector *pmask, double *resp_vals);

extern void
fill_weight(gsl_vector *weights);

extern void
get_troughput_table_name(char *filename, int beamID, char *table_name);

extern double
get_response_value_plus(const spectrum *resp, const double wavelength,
                        int *nguess);

extern void
get_response_values(response_function *resp_func, double wavelength, double* resp_vals);

extern  response_function *
create_response_function(char *filename);

extern  void
free_response_function(response_function *resp_func);

extern calib_function *
get_calfunc_for_beam(const beam actbeam, const int for_grism, char CONF_file[], const aperture_conf * conf);

#endif


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

#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_multilarge_nlinear.h>

gsl_multilarge_nlinear_workspace *
gsl_multilarge_nlinear_alloc (const gsl_multilarge_nlinear_type * T,
                              const gsl_multilarge_nlinear_parameters * params,
                              const size_t n, const size_t p)
{
  gsl_multilarge_nlinear_workspace * w;

  if (n < p)
    {
      GSL_ERROR_VAL ("insufficient data points, n < p", GSL_EINVAL, 0);
    }

  w = calloc (1, sizeof (gsl_multilarge_nlinear_workspace));
  if (w == 0)
    {
      GSL_ERROR_VAL ("failed to allocate space for workspace",
                     GSL_ENOMEM, 0);
    }

  w->n = n;
  w->p = p;
  w->type = T;
  w->fdf = NULL;
  w->niter = 0;
  w->params = *params;

  /* the cgst method uses its own built-in linear solver */
  if (w->params.trs == gsl_multilarge_nlinear_trs_cgst)
    {
      w->params.solver = gsl_multilarge_nlinear_solver_none;
    }

  w->x = gsl_vector_calloc (p);
  if (w->x == 0)
    {
      gsl_multilarge_nlinear_free (w);
      GSL_ERROR_VAL ("failed to allocate space for x", GSL_ENOMEM, 0);
    }

  w->f = gsl_vector_calloc (n);
  if (w->f == 0)
    {
      gsl_multilarge_nlinear_free (w);
      GSL_ERROR_VAL ("failed to allocate space for f", GSL_ENOMEM, 0);
    }

  w->dx = gsl_vector_calloc (p);
  if (w->dx == 0)
    {
      gsl_multilarge_nlinear_free (w);
      GSL_ERROR_VAL ("failed to allocate space for dx", GSL_ENOMEM, 0);
    }

  w->g = gsl_vector_alloc (p);
  if (w->g == 0)
    {
      gsl_multilarge_nlinear_free (w);
      GSL_ERROR_VAL ("failed to allocate space for g", GSL_ENOMEM, 0);
    }

  if (w->params.solver == gsl_multilarge_nlinear_solver_cholesky ||
      w->params.solver == gsl_multilarge_nlinear_solver_mcholesky)
    {
      w->JTJ = gsl_matrix_alloc (p, p);
      if (w->JTJ == 0)
        {
          gsl_multilarge_nlinear_free (w);
          GSL_ERROR_VAL ("failed to allocate space for JTJ", GSL_ENOMEM, 0);
        }
    }

  w->sqrt_wts_work = gsl_vector_calloc (n);
  if (w->sqrt_wts_work == 0)
    {
      gsl_multilarge_nlinear_free (w);
      GSL_ERROR_VAL ("failed to allocate space for weights", GSL_ENOMEM, 0);
    }

  w->state = (T->alloc)(&(w->params), n, p);
  if (w->state == 0)
    {
      gsl_multilarge_nlinear_free (w);
      GSL_ERROR_VAL ("failed to allocate space for state", GSL_ENOMEM, 0);
    }

  return w;
}

void
gsl_multilarge_nlinear_free (gsl_multilarge_nlinear_workspace * w)
{
  RETURN_IF_NULL (w);

  if (w->state)
    (w->type->free) (w->state);

  if (w->dx)
    gsl_vector_free (w->dx);

  if (w->x)
    gsl_vector_free (w->x);

  if (w->f)
    gsl_vector_free (w->f);

  if (w->sqrt_wts_work)
    gsl_vector_free (w->sqrt_wts_work);

  if (w->g)
    gsl_vector_free (w->g);

  if (w->JTJ)
    gsl_matrix_free (w->JTJ);

  free (w);
}

gsl_multilarge_nlinear_parameters
gsl_multilarge_nlinear_default_parameters(void)
{
  gsl_multilarge_nlinear_parameters params;

  params.trs = gsl_multilarge_nlinear_trs_lm;
  params.scale = gsl_multilarge_nlinear_scale_more;
  params.solver = gsl_multilarge_nlinear_solver_cholesky;
  params.fdtype = GSL_MULTILARGE_NLINEAR_FWDIFF;
  params.factor_up = 3.0;
  params.factor_down = 2.0;
  params.avmax = 0.75;
  params.h_df = GSL_SQRT_DBL_EPSILON;
  params.h_fvv = 0.01;
  params.max_iter = 0;
  params.tol = 1.0e-6;

  return params;
}

int
gsl_multilarge_nlinear_init (const gsl_vector * x,
                             gsl_multilarge_nlinear_fdf * fdf,
                             gsl_multilarge_nlinear_workspace * w)
{
  return gsl_multilarge_nlinear_winit(x, NULL, fdf, w);
}

int
gsl_multilarge_nlinear_winit (const gsl_vector * x,
                              const gsl_vector * wts,
                              gsl_multilarge_nlinear_fdf * fdf,
                              gsl_multilarge_nlinear_workspace * w)
{
  const size_t n = w->f->size;

  if (n != fdf->n)
    {
      GSL_ERROR ("function size does not match workspace", GSL_EBADLEN);
    }
  else if (w->x->size != x->size)
    {
      GSL_ERROR ("vector length does not match workspace", GSL_EBADLEN);
    }
  else if (wts != NULL && n != wts->size)
    {
      GSL_ERROR ("weight vector length does not match workspace", GSL_EBADLEN);
    }
  else
    {
      size_t i;

      /* initialize counters for function and Jacobian evaluations */
      fdf->nevalf = 0;
      fdf->nevaldfu = 0;
      fdf->nevaldf2 = 0;
      fdf->nevalfvv = 0;

      w->fdf = fdf;
      gsl_vector_memcpy(w->x, x);
      w->niter = 0;

      if (wts)
        {
          w->sqrt_wts = w->sqrt_wts_work;

          for (i = 0; i < n; ++i)
            {
              double wi = gsl_vector_get(wts, i);
              gsl_vector_set(w->sqrt_wts, i, sqrt(wi));
            }
        }
      else
        {
          w->sqrt_wts = NULL;
        }

      return (w->type->init) (w->state, w->sqrt_wts, w->fdf,
                              w->x, w->f, w->g, w->JTJ);
    }
}

int
gsl_multilarge_nlinear_iterate (gsl_multilarge_nlinear_workspace * w)
{
  int status =
    (w->type->iterate) (w->state, w->sqrt_wts, w->fdf,
                        w->x, w->f, w->g, w->JTJ, w->dx);

  w->niter++;

  return status;
}

double
gsl_multilarge_nlinear_avratio (const gsl_multilarge_nlinear_workspace * w)
{
  return (w->type->avratio) (w->state);
}

int
gsl_multilarge_nlinear_rcond (double * rcond, const gsl_multilarge_nlinear_workspace * w)
{
  int status = (w->type->rcond) (rcond, w->JTJ, w->state);
  return status;
}

int
gsl_multilarge_nlinear_covar (gsl_matrix * covar, gsl_multilarge_nlinear_workspace * w)
{
  if (covar->size1 != covar->size2)
    {
      GSL_ERROR ("covariance matrix must be square", GSL_ENOTSQR);
    }
  else if (covar->size1 != w->p)
    {
      GSL_ERROR ("covariance matrix does not match workspace", GSL_EBADLEN);
    }
  else
    {
      int status = (w->type->covar) (w->JTJ, covar, w->state);
      return status;
    }
}

/*
gsl_multilarge_nlinear_driver()
  Iterate the nonlinear least squares solver until completion

Inputs: maxiter  - maximum iterations to allow
        xtol     - tolerance in step x
        gtol     - tolerance in gradient
        ftol     - tolerance in ||f||
        callback - callback function to call each iteration
        callback_params - parameters to pass to callback function
        info     - (output) info flag on why iteration terminated
                   1 = stopped due to small step size ||dx|
                   2 = stopped due to small gradient
                   3 = stopped due to small change in f
                   GSL_ETOLX = ||dx|| has converged to within machine
                               precision (and xtol is too small)
                   GSL_ETOLG = ||g||_inf is smaller than machine
                               precision (gtol is too small)
                   GSL_ETOLF = change in ||f|| is smaller than machine
                               precision (ftol is too small)
        w        - workspace

Return:
GSL_SUCCESS if converged
GSL_MAXITER if maxiter exceeded without converging
GSL_ENOPROG if no accepted step found on first iteration
*/

int
gsl_multilarge_nlinear_driver (const size_t maxiter,
                               const double xtol,
                               const double gtol,
                               const double ftol,
                               void (*callback)(const size_t iter, void *params,
                                                const gsl_multilarge_nlinear_workspace *w),
                               void *callback_params,
                               int *info,
                               gsl_multilarge_nlinear_workspace * w)
{
  int status;
  size_t iter = 0;

  /* call user callback function prior to any iterations
   * with initial system state */
  if (callback)
    callback(iter, callback_params, w);

  do
    {
      status = gsl_multilarge_nlinear_iterate (w);

      /*
       * If the solver reports no progress on the first iteration,
       * then it didn't find a single step to reduce the
       * cost function and more iterations won't help so return.
       *
       * If we get a no progress flag on subsequent iterations,
       * it means we did find a good step in a previous iteration,
       * so continue iterating since the solver has now reset
       * mu to its initial value.
       */
      if (status == GSL_ENOPROG && iter == 0)
        {
          *info = status;
          return GSL_EMAXITER;
        }

      ++iter;

      if (callback)
        callback(iter, callback_params, w);

      /* test for convergence */
      status = gsl_multilarge_nlinear_test(xtol, gtol, ftol, info, w);
    }
  while (status == GSL_CONTINUE && iter < maxiter);

  /*
   * the following error codes mean that the solution has converged
   * to within machine precision, so record the error code in info
   * and return success
   */
  if (status == GSL_ETOLF || status == GSL_ETOLX || status == GSL_ETOLG)
    {
      *info = status;
      status = GSL_SUCCESS;
    }

  /* check if max iterations reached */
  if (iter >= maxiter && status != GSL_SUCCESS)
    status = GSL_EMAXITER;

  return status;
} /* gsl_multilarge_nlinear_driver() */

const char *
gsl_multilarge_nlinear_name (const gsl_multilarge_nlinear_workspace * w)
{
  return w->type->name;
}

gsl_vector *
gsl_multilarge_nlinear_position (const gsl_multilarge_nlinear_workspace * w)
{
  return w->x;
}

gsl_vector *
gsl_multilarge_nlinear_residual (const gsl_multilarge_nlinear_workspace * w)
{
  return w->f;
}

gsl_vector *
gsl_multilarge_nlinear_step (const gsl_multilarge_nlinear_workspace * w)
{
  return w->dx;
}

size_t
gsl_multilarge_nlinear_niter (const gsl_multilarge_nlinear_workspace * w)
{
  return w->niter;
}

const char *
gsl_multilarge_nlinear_trs_name (const gsl_multilarge_nlinear_workspace * w)
{
  return w->params.trs->name;
}

/*
gsl_multilarge_nlinear_eval_f()
  Compute residual vector y with user callback function, and apply
weighting transform if given:

y~ = sqrt(W) y

Inputs: fdf  - callback function
        x    - model parameters
        swts - weight matrix sqrt(W) = sqrt(diag(w1,w2,...,wn))
               set to NULL for unweighted fit
        y    - (output) (weighted) residual vector
               y_i = sqrt(w_i) f_i where f_i is unweighted residual
*/

int
gsl_multilarge_nlinear_eval_f(gsl_multilarge_nlinear_fdf *fdf,
                              const gsl_vector *x,
                              const gsl_vector *swts,
                              gsl_vector *y)
{
  int s = ((*((fdf)->f)) (x, fdf->params, y));

  ++(fdf->nevalf);

  /* y <- sqrt(W) y */
  if (swts)
    gsl_vector_mul(y, swts);

  return s;
}

/*
gsl_multilarge_nlinear_eval_df()
  Compute Jacobian matrix-vector product:

v = J * u

or

v = J^T u

Inputs: TransJ - use J or J^T
        x      - model parameters
        f      - residual vector f(x)
        u      - input vector u
        swts   - weight matrix W = diag(w1,w2,...,wn)
                 set to NULL for unweighted fit
        h      - finite difference step size
        fdtype - finite difference method
        fdf    - callback function
        v      - (output) vector v
        JTJ    - (output) matrix J^T J
        work   - workspace for finite difference, size n
*/

int
gsl_multilarge_nlinear_eval_df(const CBLAS_TRANSPOSE_t TransJ,
                               const gsl_vector *x,
                               const gsl_vector *f,
                               const gsl_vector *u,
                               const gsl_vector *swts,
                               const double h,
                               const gsl_multilarge_nlinear_fdtype fdtype,
                               gsl_multilarge_nlinear_fdf *fdf,
                               gsl_vector *v,
                               gsl_matrix *JTJ,
                               gsl_vector *work)
{
  const size_t n = fdf->n;
  const size_t p = fdf->p;

  if (u != NULL && ((TransJ == CblasNoTrans && u->size != p) ||
                    (TransJ == CblasTrans && u->size != n)))
    {
      GSL_ERROR("u vector has wrong size", GSL_EBADLEN);
    }
  else if (v != NULL && ((TransJ == CblasNoTrans && v->size != n) ||
                         (TransJ == CblasTrans && v->size != p)))
    {
      GSL_ERROR("v vector has wrong size", GSL_EBADLEN);
    }
  else if (JTJ != NULL && ((JTJ->size1 != p) || (JTJ->size2 != p)))
    {
      GSL_ERROR("JTJ matrix has wrong size", GSL_EBADLEN);
    }
  else
    {
      int status = GSL_SUCCESS;

      if (fdf->df)
        {
          /* call user-supplied function */
          status = ((*((fdf)->df)) (TransJ, x, u, fdf->params, v, JTJ));

          if (v)
            ++(fdf->nevaldfu);

          if (JTJ)
            ++(fdf->nevaldf2);
        }
      else
        {
#if 0
          /* use finite difference Jacobian approximation */
          status = gsl_multilarge_nlinear_df(h, fdtype, x, swts, fdf, f, df, work);
#endif
        }

      return status;
    }
}

/*
gsl_multilarge_nlinear_eval_fvv()
  Compute second direction derivative vector yvv with user
callback function, and apply weighting transform if given:

yvv~ = sqrt(W) yvv

Inputs: h    - step size for finite difference, if needed
        x    - model parameters, size p
        v    - unscaled geodesic velocity vector, size p
        f    - residual vector f(x), size n
        swts - weight matrix sqrt(W) = sqrt(diag(w1,w2,...,wn))
               set to NULL for unweighted fit
        fdf  - callback function
        yvv  - (output) (weighted) second directional derivative vector
               yvv_i = sqrt(w_i) fvv_i where f_i is unweighted
        work - workspace, size p
*/

int
gsl_multilarge_nlinear_eval_fvv(const double h,
                                const gsl_vector *x,
                                const gsl_vector *v,
                                const gsl_vector *f,
                                const gsl_vector *swts,
                                gsl_multilarge_nlinear_fdf *fdf,
                                gsl_vector *yvv,
                                gsl_vector *work)
{
  int status = GSL_SUCCESS;

  if (fdf->fvv != NULL)
    {
      /* call user-supplied function */
      status = ((*((fdf)->fvv)) (x, v, fdf->params, yvv));
      ++(fdf->nevalfvv);
    }
  else
    {
#if 0
      /* use finite difference approximation */
      status = gsl_multilarge_nlinear_fdfvv(h, x, v, f, J,
                                          swts, fdf, yvv, work);
#endif
    }

  /* yvv <- sqrt(W) yvv */
  if (swts)
    gsl_vector_mul(yvv, swts);

  return status;
}


#ifndef ConditionalTest_SERIAL_H
#define ConditionalTest_SERIAL_H
#include <ace/core/core.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_randist.h>
#include "conditionaltest.h"

class ConditionalTest::Serial : public EAbstractAnalyticSerial
{
    Q_OBJECT
public:
    explicit Serial(ConditionalTest* parent);
    virtual std::unique_ptr<EAbstractAnalyticBlock> execute(const EAbstractAnalyticBlock* block) override final;

    // helper functions
    int test(CCMatrix::Pair& ccmPair, qint32 clusterIndex, qint32& testIndex, qint32 featureIndex, qint32 labelIndex, QVector<QVector<double>>& pValues, QVector<QVector<double>>& r2);
    int prepAnxData(QString testLabel, int dataIndex, TESTTYPE testType);
    bool isEmpty(QVector<QVector<double>>& matrix);
    int clusterInfo(CCMatrix::Pair& ccmPair, int clusterIndex, QString label, TESTTYPE testType);

    // Binomial Tests
    double binomial();
    double testOne();
    double testTwo();

    // Hypergeometrix Test.
    double hypergeom(CCMatrix::Pair& ccmPair, int clusterIndex, QString test_label);

    // Regression Test
    void regression(QVector<QString> &amxInfo, CCMatrix::Pair& ccmPair, int clusterIndex, TESTTYPE testType, QVector<double>& results);
    double fTest(double chisq, gsl_matrix* X, gsl_vector* Y, gsl_matrix* cov, gsl_vector* C);

private:
    /*!
     * Pointer to the serials objects parent KNNAnalytic.
     */
    ConditionalTest* _base;
    /*!
     * Annotation matrix data for testing.
     */
    QVector<QString> _amxData;
    /*!
     * Category count.
     */
    qint32 _catCount {0};
    /*!
     * Category count in cluster.
     */
    qint32 _catInCluster {0};
    /*!
     * Size of the cluster.
     */
    qint32 _clusterSize {0};
};

#endif


/**
 *
 * @file qwrapper_zlansy.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 Julien Langou
 * @author Henricus Bouwmeester
 * @author Mathieu Faverge
 * @date 2010-11-15
 * @precisions normal z -> c d s
 *
 **/
#include <lapacke.h>
#include "common.h"

/***************************************************************************//**
 *
 **/
void QUARK_CORE_zlansy(Quark *quark, Quark_Task_Flags *task_flags,
                       int norm, PLASMA_enum uplo, int N,
                       const PLASMA_Complex64_t *A, int LDA, int szeA,
                       int szeW, double *result)
{
    szeW = max(1, szeW);
    DAG_CORE_LANSY;
    QUARK_Insert_Task(quark, CORE_zlansy_quark, task_flags,
        sizeof(PLASMA_enum),                &norm,  VALUE,
        sizeof(PLASMA_enum),                &uplo,  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_zlansy_f1(Quark *quark, Quark_Task_Flags *task_flags,
                          PLASMA_enum norm, PLASMA_enum uplo, 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_LANSY;

    if ( result == fake ) {
        QUARK_Insert_Task(quark, CORE_zlansy_quark, task_flags,
            sizeof(PLASMA_enum),                &norm,  VALUE,
            sizeof(PLASMA_enum),                &uplo,  VALUE,
            sizeof(int),                        &N,     VALUE,
            sizeof(PLASMA_Complex64_t)*szeA,     A,             INPUT,
            sizeof(int),                        &LDA,   VALUE,
            sizeof(double)*szeW,                 NULL,          SCRATCH,
            sizeof(double)*szeF,                 result,        OUTPUT | GATHERV,
            0);
    } else {
        QUARK_Insert_Task(quark, CORE_zlansy_f1_quark, task_flags,
            sizeof(PLASMA_enum),                &norm,  VALUE,
            sizeof(PLASMA_enum),                &uplo,  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_zlansy_quark = PCORE_zlansy_quark
#define CORE_zlansy_quark PCORE_zlansy_quark
#endif
void CORE_zlansy_quark(Quark *quark)
{
    double *normA;
    int norm;
    PLASMA_enum uplo;
    int N;
    PLASMA_Complex64_t *A;
    int LDA;
    double *work;

    quark_unpack_args_7(quark, norm, uplo, N, A, LDA, work, normA);
    *normA = LAPACKE_zlansy_work(
        LAPACK_COL_MAJOR,
        lapack_const(norm), lapack_const(uplo),
        N, A, LDA, work);
}

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

    quark_unpack_args_8(quark, norm, uplo, N, A, LDA, work, normA, fake);
    *normA = LAPACKE_zlansy_work(
        LAPACK_COL_MAJOR,
        lapack_const(norm),
        lapack_const(uplo),
        N, A, LDA, work);
}