#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "parmt_postProcess.h"
#ifdef PARMT_USE_INTEL
#include <mkl_cblas.h>
#else
#include <cblas.h>
#endif
#include "compearth.h"
#include "iscl/array/array.h"
#include "iscl/memory/memory.h"
#include "iscl/os/os.h"

static void getBaseAndExp(const double val, double *base, int *exp);
static void setFillColor(const int i, const int iopt, char color[32]);

/*!
 * @brief Writes the global station distribution of stations, the
 *        station names, the epicenter or moment tensor, and, if
 *        desired, some indication of station polarity.
 */
int postmt_gmtHelper_writeGlobalMap(
    struct globalMapOpts_struct globalMap,
    const int nobs, const struct sacData_struct *data)
{
    const char *fcnm = "postmt_gmtHelper_writeGlobalMap\0";
    FILE *ofl;
    char *dirName, line[256], cpick[8];
    int *pol, i, ierr, l, nw, nwd, nwu;
    size_t lenos;
    const char *forwardSlash = "/";
    const int nTimeVars = 11;
    const enum sacHeader_enum timeVarNames[11]
       = {SAC_CHAR_KA,
          SAC_CHAR_KT0, SAC_CHAR_KT1, SAC_CHAR_KT2, SAC_CHAR_KT3,
          SAC_CHAR_KT4, SAC_CHAR_KT5, SAC_CHAR_KT6, SAC_CHAR_KT7,
          SAC_CHAR_KT8, SAC_CHAR_KT9};

    dirName = os_dirname(globalMap.outputScript, &ierr);
    if (!os_path_isdir(dirName))
    {
        ierr = os_makedirs(dirName);
        if (ierr != 0)
        {
            printf("%s: Failed to make output directory: %s\n", fcnm, dirName);
            return -1;
        }
    }
    memory_free8c(&dirName);

    ofl = fopen(globalMap.outputScript, "w");
    fprintf(ofl, "#!/bin/bash\n");
    fprintf(ofl, "outps=%s\n", globalMap.psFile);
    fprintf(ofl, "olat=%f\n", globalMap.evla);
    fprintf(ofl, "olon=%f\n", globalMap.evlo);
    fprintf(ofl, "J=-JH${olon}%s6i\n", forwardSlash);
    fprintf(ofl, "R=-Rg\n");
    fprintf(ofl, "gmt pscoast $J $R -B0g30 -Di -Ggray -P -K > ${outps}\n");
    fprintf(ofl, "ts=0.15i\n");

    fprintf(ofl, "# Draw great circle arcs between source and receivers\n");
    fprintf(ofl, "gmt psxy $J $R -W1p -O -K << EOF >> ${outps}\n");
    for (i=0; i<nobs; i++)
    {
        fprintf(ofl, "%8.3f %8.3f\n", data[i].header.stlo, data[i].header.stla);
        fprintf(ofl, "%8.3f %8.3f\n", globalMap.evlo, globalMap.evla);
        if (i < nobs - 1){fprintf(ofl, "\n");}
    }
    fprintf(ofl, "EOF\n");

    fprintf(ofl, "# Plot the moment tensor\n");
    postmt_gmtHelper_makePsmecaLine(globalMap.basis, globalMap.mts,
                                    globalMap.evla, globalMap.evlo,
                                    globalMap.evdp, "Optimum\0", line);
    if (globalMap.lwantMT)
    {
        fprintf(ofl, "gmt psmeca $J $R -Sm0.15i -M -N -Gblue -W0.5p,black -O -K << EOF >> ${outps}\n");
        fprintf(ofl, "%s", line);
        fprintf(ofl, "EOF\n");
    }
    else
    {
        fprintf(ofl, "# Plot the epicenter\n");
        fprintf(ofl, "gmt psxy $J $R -Sa${ts} -Gblue -Wblack -O -K << EOF >> ${outps}\n");
        fprintf(ofl, "%8.3f %8.3f\n", globalMap.evlo, globalMap.evla);
        fprintf(ofl, "EOF\n");
    }

    if (!globalMap.lwantPolarity)
    {
        fprintf(ofl, "# Plot the stations\n");
        fprintf(ofl, "gmt psxy $J $R -St${ts} -Gred -Wblack -O -K << EOF >> ${outps}\n");
        for (i=0; i<nobs; i++)
        {
            fprintf(ofl, "%8.3f %8.3f %s\n", data[i].header.stlo,
                    data[i].header.stla, data[i].header.kstnm);
        }
        fprintf(ofl, "EOF\n");
    }
    else
    {
        // Get the polarities
        nw = 0;
        nwd = 0;
        nwu = 0;
        pol = (int *) calloc((size_t) nobs, sizeof(int));
        for (i=0; i<nobs; i++)
        {
            memset(cpick, 0, 8*sizeof(char));
            for (l=0; l<nTimeVars; l++)
            {
                ierr = sacio_getCharacterHeader(timeVarNames[l],
                                                data[i].header, cpick);
                if (ierr == 0){break;}
            }
            lenos = strlen(cpick);
            if (lenos > 0)
            {
                if (cpick[lenos-1] == '+')
                {
                    nwu = nwu + 1;
                    pol[i] = 1;
                }
                else if (cpick[lenos-1] == '-')
                {
                    nwd = nwd + 1;
                    pol[i] =-1;
                }
                else
                {
                    nw = nw + 1;
                }
            }
            else
            {
                nw = nw + 1;
            }
        }
        // Write the unknowns
        if (nw > 0)
        {
            fprintf(ofl, "# Plot the indeterminant stations\n");
            fprintf(ofl, "gmt psxy $J $R -Ss${ts} -Gred -Wblack -O -K << EOF >> ${outps}\n");
            for (i=0; i<nobs; i++)
            {
                if (pol[i] == 0)
                {
                    fprintf(ofl, "%8.3f %8.3f %s\n", data[i].header.stlo,
                            data[i].header.stla, data[i].header.kstnm);
                }
            }
            fprintf(ofl, "EOF\n");
        }
        // Write the ups
        if (nwu > 0)
        {
            fprintf(ofl, "# Plot the upward stations\n");
            fprintf(ofl, "gmt psxy $J $R -St${ts} -Gred -Wblack -O -K << EOF >> ${outps}\n");
            for (i=0; i<nobs; i++)
            {
                if (pol[i] == 1)
                {
                    fprintf(ofl, "%8.3f %8.3f %s\n", data[i].header.stlo,
                            data[i].header.stla, data[i].header.kstnm);
                }
            }
            fprintf(ofl, "EOF\n");
        }
        // Write the downs
        if (nwd > 0)
        {
            fprintf(ofl, "# Plot the down stations\n");
            fprintf(ofl, "gmt psxy $J $R -Si${ts} -Gred -Wblack -O -K << EOF >> ${outps}\n");
            for (i=0; i<nobs; i++)
            {
                if (pol[i] ==-1)
                {
                    fprintf(ofl, "%8.3f %8.3f %s\n", data[i].header.stlo,
                            data[i].header.stla, data[i].header.kstnm);
                }
            }
            fprintf(ofl, "EOF\n");
        }
        free(pol);
    }

    fprintf(ofl, "# Plot the station names\n");
    fprintf(ofl, "gmt pstext $J $R -F+f8p+a0+jCT -D0%s-4p -O << EOF >> ${outps}\n",
            forwardSlash);
    for (i=0; i<nobs; i++)
    {
        fprintf(ofl, "%8.3f %8.3f %s\n", data[i].header.stlo,
                data[i].header.stla, data[i].header.kstnm);
    }
    fprintf(ofl, "EOF\n");
    fprintf(ofl, "gmt psconvert -A -Tj ${outps}\n");
    fprintf(ofl, "rm ${outps}\n");
    fclose(ofl);
    chmod(globalMap.outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_writeThetaBoxes(const bool lappend, const bool lclose,
                                     const char *outputScript,
                                     const char *psFile,
                                     const int nt,
                                     const int ithetaOpt,
                                     const double *__restrict__ thetas,
                                     const double *__restrict__ thetaHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *cumTheta, *h, hAvg, thetaAvg, tmax;
    int i, ierr;
    const bool lwritePrior = true;
    tmax = array_max64f(nt, thetaHist, &ierr);
    // Compute h
    h = memory_calloc64f(nt);
    compearth_theta2h(nt, thetas, h);
    thetaAvg = -30.0;
    memset(app, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "parms=\"--FONT_LABEL=10p --MAP_LABEL_OFFSET=0.1c --PROJ_LENGTH_UNIT=cm\"\n");
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
            "gmt psbasemap -JX5i/1i %s -R0/90/0/%.2f -Bg10a10:\"Dips (deg)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R0/90/0/%.2f -Bpxg10a10+l:\"Dips (deg)\" -Bpya%.2f+l:\"Likelihood\" -BWSnn -P %s -K ${parms} >%s ${psfl}\n",
            shift, tmax*1.1, tmax*0.2, app, more);
    setFillColor(0, ithetaOpt, color);
    if (nt > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%f %f\n", 0.0, 0.0);
    fprintf(ofl, "%f %f\n", 0.0, thetaHist[0]);
    for (i=0; i<nt-1; i++)
    {
        hAvg = 0.5*(h[i] + h[i+1]);
        compearth_h2theta(1, &hAvg, &thetaAvg);
        fprintf(ofl, "%f %f\n", thetaAvg*180.0/M_PI, thetaHist[i]);
        fprintf(ofl, "%f %f\n", thetaAvg*180.0/M_PI, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, ithetaOpt, color);
        if (i < nt - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%f %f\n", thetaAvg*180.0/M_PI, 0.0);
        fprintf(ofl, "%f %f\n", thetaAvg*180.0/M_PI, thetaHist[i+1]);
    }
    fprintf(ofl, "%f %f\n", 90.0, thetaHist[nt-1]);
    fprintf(ofl, "%f %f\n", 90.0, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n", 00.0, 1.0/(double) nt);
        fprintf(ofl, "%f %f\n", 90.0, 1.0/(double) nt);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R0/90/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n");
*/
    fprintf(ofl, "gmt psbasemap -R0/90/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n");
    cumTheta = array_cumsum64f(nt, thetaHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<nt; i++)
    {
        fprintf(ofl, "%f %f\n", thetas[i]*180/M_PI, cumTheta[i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
        fprintf(ofl, "gmt psconvert -A -Tf ${psfl}\n");
        fprintf(ofl, "/bin/rm ${psfl}\n");
    }
    memory_free64f(&cumTheta);
    memory_free64f(&h);
    fclose(ofl);
    chmod(outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_writeSigmaBoxes(const bool lappend, const bool lclose,
                                     const char *outputScript,
                                     const char *psFile,
                                     const int ns,
                                     const int isigmaOpt,
                                     const double *__restrict__ sigmas,
                                     const double *__restrict__ sigmaHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *cumSigma, ds, smax;
    int i, ierr;
    const bool lwritePrior = true;
    smax = array_max64f(ns, sigmaHist, &ierr);
    // Take averages
    ds = 0.0;
    memset(app, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "parms=\"--FONT_LABEL=10p --MAP_LABEL_OFFSET=0.1c --PROJ_LENGTH_UNIT=cm\"\n");
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
           "gmt psbasemap -JX5i/1i %s -R-90/90/0/%.2f -Bg15a15:\"Slips (deg)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R-90/90/0/%.2f -Bpxg15a15+l\"Slips (deg)\" -Bpya%.2f+l\"Likelihood\" -BWSn -P %s -K ${parms} >%s ${psfl}\n",
            shift, smax*1.1, smax*0.2, app, more);
    setFillColor(0, isigmaOpt, color);
    if (ns > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
       ds = (sigmas[1] - sigmas[0])*180.0/M_PI;
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%.2f %f\n", -90.0, 0.0);
    fprintf(ofl, "%.2f %f\n", -90.0, sigmaHist[0]);
    for (i=0; i<ns-1; i++)
    {
        fprintf(ofl, "%.2f %f\n", -90.0 + (double) (i+1)*ds, sigmaHist[i]);
        fprintf(ofl, "%.2f %f\n", -90.0 + (double) (i+1)*ds, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, isigmaOpt, color);
        if (i < ns - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%.2f %f\n", -90.0 + (double) (i+1)*ds, 0.0);
        fprintf(ofl, "%.2f %f\n", -90.0 + (double) (i+1)*ds, sigmaHist[i+1]);
    }
    fprintf(ofl, "%.2f %f\n", 90.0, sigmaHist[ns-1]);
    fprintf(ofl, "%.2f %f\n", 90.0, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n",-90.0, 1.0/(double) ns);
        fprintf(ofl, "%f %f\n", 90.0, 1.0/(double) ns);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R-90/90/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n");
*/
    fprintf(ofl, "gmt psbasemap -R-90/90/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n");
    cumSigma = array_cumsum64f(ns, sigmaHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<ns; i++)
    {
        fprintf(ofl, "%f %f\n", sigmas[i]*180/M_PI, cumSigma[i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
        fprintf(ofl, "gmt psconvert -A -Tf ${psfl}\n");
        fprintf(ofl, "/bin/rm ${psfl}\n");
    }
    memory_free64f(&cumSigma);
    fclose(ofl);
    chmod(outputScript, 0755);
    return 0;
}

//============================================================================//
int postmt_gmtHelper_writeKappaBoxes(const bool lappend, const bool lclose,
                                     const char *outputScript,
                                     const char *psFile,
                                     const int nk,
                                     const int kappaOpt,
                                     const double *__restrict__ kappas,
                                     const double *__restrict__ kappaHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *cumKappa, dk, kmax;
    int i, ierr;
    const bool lwritePrior = true;
    kmax = array_max64f(nk, kappaHist, &ierr);
    // Take averages
    dk = 0.0;
    memset(app, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "parms=\"--FONT_LABEL=10p --MAP_LABEL_OFFSET=0.1c --PROJ_LENGTH_UNIT=cm\"\n");
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
            "gmt psbasemap -JX5i/1i %s -R0/360/0/%.2f -Bg30a30:\"Strike (deg)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R0/360/0/%.2f -Bpxg30a30+l\"Strike (deg)\" -Bpya%.2f+l\"Likelihood\" -BWSn -P %s -K ${parms} >%s ${psfl}\n",
            shift, kmax*1.1, kmax*0.2, app, more);
    setFillColor(0, kappaOpt, color);
    if (nk > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
       dk = (kappas[1] - kappas[0])*180.0/M_PI;
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%.2f %f\n", 0.0, 0.0);
    fprintf(ofl, "%.2f %f\n", 0.0, kappaHist[0]);
    for (i=0; i<nk-1; i++)
    {
        fprintf(ofl, "%.2f %f\n", 0.0 + (double) (i+1)*dk, kappaHist[i]);
        fprintf(ofl, "%.2f %f\n", 0.0 + (double) (i+1)*dk, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, kappaOpt, color);
        if (i < nk - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%.2f %f\n", 0.0 + (double) (i+1)*dk, 0.0);
        fprintf(ofl, "%.2f %f\n", 0.0 + (double) (i+1)*dk, kappaHist[i+1]);
    }
    fprintf(ofl, "%.2f %f\n", 360.0, kappaHist[nk-1]);
    fprintf(ofl, "%.2f %f\n", 360.0, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n", 0.0,   1.0/(double) nk);
        fprintf(ofl, "%f %f\n", 360.0, 1.0/(double) nk);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R0/360/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n");
*/
    fprintf(ofl, "gmt psbasemap -R0/360/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n");
    cumKappa = array_cumsum64f(nk, kappaHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<nk; i++)
    {
        fprintf(ofl, "%f %f\n", kappas[i]*180.0/M_PI, cumKappa[i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
        fprintf(ofl, "gmt psconvert -A -Tf ${psfl}\n");
        fprintf(ofl, "/bin/rm ${psfl}\n");
    }
    memory_free64f(&cumKappa);
    fclose(ofl);
    chmod(outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_writeDepthBoxes(const bool lappend, const bool lclose,
                                     const char *outputScript,
                                     const char *psFile,
                                     const int nd,
                                     const int idepOpt,
                                     const double *__restrict__ deps,
                                     const double *__restrict__ depHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *cumDep, dd, dmax, depMin, depMax;
    int i, ierr;
    const bool lwritePrior = true;
    // Compute moment magnitudes
    dmax = array_max64f(nd, depHist, &ierr);
    dd = 0.1;
    if (nd > 1){dd = deps[1] - deps[0];};
    depMin = fmax(0.0, deps[0]    - dd/2.0);
    depMax = fmax(0.0, deps[nd-1] + dd/2.0);
    memset(app, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
            "gmt psbasemap -JX5i/1i %s -R%.1f/%.1f/0/%.2f -Bg%fa%f:\"Depths (km)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R%.1f/%.1f/0/%.2f -Bpxg%fa%f+l\"Depths (km)\" -Bpya%.2f+l\"Likelihood\" -BWSn -P %s -K ${parms} >%s ${psfl}\n",
            shift, depMin, depMax, dmax*1.1, dd, (nd-1)*dd/5.0, dmax*0.2, app, more);
    setFillColor(0, idepOpt, color);
    if (nd > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%.2f %f\n", depMin, 0.0);
    fprintf(ofl, "%.2f %f\n", depMin, depHist[0]);
    for (i=0; i<nd-1; i++)
    {
        fprintf(ofl, "%.2f %f\n", depMin + (double) (i+1)*dd, depHist[i]);
        fprintf(ofl, "%.2f %f\n", depMin + (double) (i+1)*dd, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, idepOpt, color);
        if (i < nd - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%.2f %f\n", depMin + (double) (i+1)*dd, 0.0);
        fprintf(ofl, "%.2f %f\n", depMin + (double) (i+1)*dd, depHist[i+1]);
    }
    fprintf(ofl, "%.2f %f\n", depMax, depHist[nd-1]);
    fprintf(ofl, "%.2f %f\n", depMax, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n", depMin, 1.0/(double) nd);
        fprintf(ofl, "%f %f\n", depMax, 1.0/(double) nd);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R%f/%f/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n", depMin, depMax);
*/
    fprintf(ofl, "gmt psbasemap -R%f/%f/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n", depMin, depMax);
    cumDep = array_cumsum64f(nd, depHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<nd; i++)
    {
        fprintf(ofl, "%f %f\n", deps[i], cumDep[i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
        fprintf(ofl, "gmt psconvert -A -Tf ${psfl}\n");
        fprintf(ofl, "/bin/rm ${psfl}\n");
    }
    memory_free64f(&cumDep);
    fclose(ofl);
    chmod(outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_writeMagnitudeBoxes(const bool lappend, const bool lclose,
                                         const char *outputScript,
                                         const char *psFile,
                                         const int nm,
                                         const int magOpt,
                                         const double *__restrict__ M0s,
                                         const double *__restrict__ magHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *cumMag, *Mw, dm, mmax, mwMin, mwMax;
    int i, ierr;
    const bool lwritePrior = true;
    // Compute moment magnitudes
    Mw = memory_calloc64f(nm);
    mmax = array_max64f(nm, magHist, &ierr);
    compearth_m02mw(nm, CE_KANAMORI_1978, M0s, Mw);
    dm = 0.1;
    if (nm > 1){dm = Mw[1] - Mw[0];};
    mwMin = Mw[0] - dm/2.0;
    mwMax = Mw[nm-1] + dm/2.0;
    memset(app, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "parms=\"--FONT_LABEL=10p --MAP_LABEL_OFFSET=0.1c --PROJ_LENGTH_UNIT=cm\"\n");
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
            "gmt psbasemap -JX5i/1i %s -R%.2f/%.2f/0/%.2f -Bg%fa%f:\"Magnitude (Mw)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R%.2f/%.2f/0/%.2f -Bpxg%fa%f+l\"Magnitude (Mw)\" -Bpya%.2f+l\"Likelihood\" -BWSn -P %s -K ${parms} >%s ${psfl}\n",
            shift, mwMin, mwMax, mmax*1.1, dm, (nm-1)*dm/5.0, mmax*0.2, app, more);
    setFillColor(0, magOpt, color);
    if (nm > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%.2f %f\n", mwMin, 0.0);
    fprintf(ofl, "%.2f %f\n", mwMin, magHist[0]);
    for (i=0; i<nm-1; i++)
    {
        fprintf(ofl, "%.2f %f\n", mwMin + (double) (i+1)*dm, magHist[i]);
        fprintf(ofl, "%.2f %f\n", mwMin + (double) (i+1)*dm, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, magOpt, color);
        if (i < nm - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%.2f %f\n", mwMin + (double) (i+1)*dm, 0.0);
        fprintf(ofl, "%.2f %f\n", mwMin + (double) (i+1)*dm, magHist[i+1]);
    }
    fprintf(ofl, "%.2f %f\n", mwMax, magHist[nm-1]);
    fprintf(ofl, "%.2f %f\n", mwMax, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n", mwMin, 1.0/(double) nm);
        fprintf(ofl, "%f %f\n", mwMax, 1.0/(double) nm);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R%f/%f/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n", mwMin, mwMax);
*/
    fprintf(ofl, "gmt psbasemap -R%f/%f/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n", mwMin, mwMax);
    cumMag = array_cumsum64f(nm, magHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<nm; i++)
    {
        fprintf(ofl, "%f %f\n", Mw[i], cumMag[i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
    }
    fclose(ofl);
    memory_free64f(&cumMag);
    memory_free64f(&Mw);
    chmod(outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_writeGammaBoxes(const bool lappend, const bool lclose,
                                     const char *outputScript,
                                     const char *psFile,
                                     const int ng,
                                     const int igammaOpt,
                                     const double *__restrict__ gammas,
                                     const double *__restrict__ gammaHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *cumGamma, *v, gmax, gammaAvg, vAvg;
    int i, ierr;
    const bool lwritePrior = true;
    gmax = array_max64f(ng, gammaHist, &ierr);
    // Compute v
    v = memory_calloc64f(ng);
    compearth_gamma2v(ng, gammas, v);
    // Take averages
    gammaAvg = -30.0;
    memset(app, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "parms=\"--FONT_LABEL=10p --MAP_LABEL_OFFSET=0.1c --PROJ_LENGTH_UNIT=cm\"\n");
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
            "gmt psbasemap -JX5i/1i %s -R-30/30/0/%.2f -Bg5a5:\"Longitude (deg)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R-30/30/0/%.2f -Bpxg5a5+l\"Longitude (deg)\" -Bpya%.2f+l\"Likelihood\" -BWSn -P %s -K ${parms} >%s ${psfl}\n",
            shift, gmax*1.1, gmax*0.2, app, more);
    setFillColor(0, igammaOpt, color);
    if (ng > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%f %f\n", -30.0, 0.0);
    fprintf(ofl, "%f %f\n", -30.0, gammaHist[0]);
    for (i=0; i<ng-1; i++)
    {
        vAvg = 0.5*(v[i] + v[i+1]);
        compearth_v2gamma(1, &vAvg, &gammaAvg);
        fprintf(ofl, "%f %f\n", gammaAvg*180.0/M_PI, gammaHist[i]);
        fprintf(ofl, "%f %f\n", gammaAvg*180.0/M_PI, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, igammaOpt, color);
        if (i < ng - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%f %f\n", gammaAvg*180.0/M_PI, 0.0);
        fprintf(ofl, "%f %f\n", gammaAvg*180.0/M_PI, gammaHist[i+1]);
    }
    fprintf(ofl, "%f %f\n", 30.0, gammaHist[ng-1]);
    fprintf(ofl, "%f %f\n", 30.0, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n",-30.0, 1.0/(double) ng);
        fprintf(ofl, "%f %f\n", 30.0, 1.0/(double) ng);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R-30/30/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n");
*/
    fprintf(ofl, "gmt psbasemap -R-30/30/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n");
    cumGamma = array_cumsum64f(ng, gammaHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<ng; i++)
    {
        fprintf(ofl, "%f %f\n", gammas[i]*180.0/M_PI, cumGamma[i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
        fprintf(ofl, "gmt psconvert -A -Tf ${psfl}\n");
        fprintf(ofl, "/bin/rm ${psfl}\n");
    }
    memory_free64f(&cumGamma);
    memory_free64f(&v);
    fclose(ofl);
    chmod(outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_writeBetaBoxes(const bool lappend, const bool lclose,
                                    const char *outputScript,
                                    const char *psFile,
                                    const int nb,
                                    const int ibetaOpt,
                                    const double *__restrict__ betas,
                                    const double *__restrict__ betaHist)
{
    FILE *ofl;
    char color[32], app[8], more[8], shift[8];
    double *betaCum, *u, bmax, betaAvg, uAvg;
    int i, ierr;
    const bool lwritePrior = true;
    bmax = array_max64f(nb, betaHist, &ierr);
    // Compute u
    u = memory_calloc64f(nb);
    compearth_beta2u(nb, betas, u);
    // Take averages
    betaAvg = 0.0;
    memset(app, 0, 8*sizeof(char));
    memset(more, 0, 8*sizeof(char));
    memset(shift, 0, 8*sizeof(char));
    if (!lappend)
    {
        ofl = fopen(outputScript, "w");
        fprintf(ofl, "#!/bin/bash\n");
        /*
        fprintf(ofl, "gmt gmtset FONT_LABEL 12p\n");
        fprintf(ofl, "gmt gmtset MAP_LABEL_OFFSET 0.1c\n");
        */
        fprintf(ofl, "parms=\"--FONT_LABEL=10p --MAP_LABEL_OFFSET=0.1c --PROJ_LENGTH_UNIT=cm\"\n");
        fprintf(ofl, "psfl=%s\n", psFile);
    }
    else
    {
        ofl = fopen(outputScript, "a");
        strcpy(app, "-O\0");
        strcpy(more, ">\0");
        strcpy(shift, "-Y4.0\0");
    }
    fprintf(ofl,
/*
            "gmt psbasemap -JX5i/1i %s -R-90/90/0/%.3f -Bg15a15:\"Latitude (deg)\":/a%.2f:\"Likelihood\":WSn -P %s -K >%s ${psfl}\n",
*/
            "gmt psbasemap -JX5i/1i %s -R-90/90/0/%.3f -Bpxg15a15+l\"Latitude (deg)\" -Bpya%.2f+l\"Likelihood\" -BWSn -P %s -K ${parms} >%s ${psfl}\n",
            shift, bmax*1.1, bmax*0.2, app, more);
    setFillColor(0, ibetaOpt, color);
    if (nb > 1)
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
    }
    else
    {
       fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
    }
    fprintf(ofl, "%f %f\n", 90.0 - 0.0, 0.0);
    fprintf(ofl, "%f %f\n", 90.0 - 0.0, betaHist[0]);
    for (i=0; i<nb-1; i++)
    {
        uAvg = 0.5*(u[i] + u[i+1]);
        compearth_u2beta(1, &uAvg, &betaAvg);
        //compearth_u2beta(1, 20, 2, &uAvg, 1.e-7, &betaAvg);
        fprintf(ofl, "%f %f\n", 90.0 - betaAvg*180.0/M_PI, betaHist[i]);
        fprintf(ofl, "%f %f\n", 90.0 - betaAvg*180.0/M_PI, 0.0);
        fprintf(ofl, "EOF\n");
        setFillColor(i+1, ibetaOpt, color);
        if (i < nb - 2 || lwritePrior)
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O -K << EOF >> ${psfl}\n", color);
        }
        else
        {
            fprintf(ofl, "gmt psxy -R -J -Wblack %s -O << EOF >> ${psfl}\n", color);
        }
        fprintf(ofl, "%f %f\n", 90.0 - betaAvg*180.0/M_PI, 0.0);
        fprintf(ofl, "%f %f\n", 90.0 - betaAvg*180.0/M_PI, betaHist[i+1]);
    }
    fprintf(ofl, "%f %f\n", 90.0 - M_PI*180.0/M_PI, betaHist[nb-1]);
    fprintf(ofl, "%f %f\n", 90.0 - M_PI*180.0/M_PI, 0.0);
    fprintf(ofl, "EOF\n");
    // Write the prior distribution
    if (lwritePrior)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,black -O -K << EOF >> ${psfl}\n");
        fprintf(ofl, "%f %f\n",-90.0, 1.0/(double) nb);
        fprintf(ofl, "%f %f\n", 90.0, 1.0/(double) nb);
        fprintf(ofl, "EOF\n");
    }
    // Write the CDF
/*
    fprintf(ofl, "gmt psbasemap -R-90/90/0/1.05 -J -Bp0.2/a0.2:\"CDF\":E -O -K >> ${psfl}\n");
*/
    fprintf(ofl, "gmt psbasemap -R-90/90/0/1.05 -J -Bpx0.2 -Bpya0.2+l\"CDF\" -BE -O -K ${parms} >> ${psfl}\n");
    betaCum = array_cumsum64f(nb, betaHist, &ierr);
    if (lclose)
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O << EOF >> ${psfl}\n");
    }
    else
    {
        fprintf(ofl, "gmt psxy -R -J -W1,blue -O -K << EOF >> ${psfl}\n");
    }
    for (i=0; i<nb; i++)
    {
        // the cdf is written backwards because i flip the axis to make it
        // increase left to right; this is opposite of what colatitude wants
        // to do
        fprintf(ofl, "%f %f\n", 90.0 - betas[i]*180.0/M_PI, betaCum[nb-1-i]);
    }
    fprintf(ofl, "EOF\n");
    if (lclose)
    {
        //fprintf(ofl, "gmt psconvert -A -Tj ${psfl}\n");
        //fprintf(ofl, "rm ${psfl}\n");
        fprintf(ofl, "gmt psconvert -A -Tf ${psfl}\n");
        fprintf(ofl, "/bin/rm ${psfl}\n");
    }
    memory_free64f(&u);
    memory_free64f(&betaCum);
    fclose(ofl);
    chmod(outputScript, 0755);
    return 0;
}
//============================================================================//
int postmt_gmtHelper_makeRegularHistograms(
    const int nlocs, const int nm,
    const int nb, const int ng, const int nk,
    const int ns, const int nt,
    const int nmt, const double *__restrict__ phi,
    double **__restrict__ locHist,
    double **__restrict__ magHist,
    double **__restrict__ betaHist,
    double **__restrict__ gammaHist,
    double **__restrict__ kappaHist,
    double **__restrict__ sigmaHist,
    double **__restrict__ thetaHist)
{
    double *betaH, *gammaH, *kappaH, *locH, *magH, *sigmaH, *thetaH, xsum;
    int ib, ierr, ig, ik, il, im, imt, is, it;
    locH = memory_calloc64f(nlocs);
    magH = memory_calloc64f(nm);
    betaH = memory_calloc64f(nb);
    gammaH = memory_calloc64f(ng);
    kappaH = memory_calloc64f(nk);
    sigmaH = memory_calloc64f(ns);
    thetaH = memory_calloc64f(nt);
    for (il=0; il<nlocs; il++)
    {
        for (im=0; im<nm; im++)
        {
            for (ib=0; ib<nb; ib++)
            {
                for (ig=0; ig<ng; ig++)
                {
                    for (ik=0; ik<nk; ik++)
                    {
                        for (is=0; is<ns; is++)
                        {
                            for (it=0; it<nt; it++)
                            {
                                imt = il*nm*nb*ng*nk*ns*nt
                                    + im*nb*ng*nk*ns*nt
                                    + ib*ng*nk*ns*nt
                                    + ig*nk*ns*nt
                                    + ik*ns*nt
                                    + is*nt
                                    + it;
                                locH[il]   = locH[il]   + phi[imt];
                                magH[im]   = magH[im]   + phi[imt];
                                betaH[ib]  = betaH[ib]  + phi[imt];
                                gammaH[ig] = gammaH[ig] + phi[imt];
                                kappaH[ik] = kappaH[ik] + phi[imt];
                                sigmaH[is] = sigmaH[is] + phi[imt];
                                thetaH[it] = thetaH[it] + phi[imt];
                            }
                        }
                    }
                }
            }
        }
    }
    xsum = array_sum64f(nmt, phi, &ierr);
    cblas_dscal(nlocs, 1.0/xsum, locH, 1);
    cblas_dscal(nm, 1.0/xsum, magH, 1);
    cblas_dscal(nb, 1.0/xsum, betaH, 1);
    cblas_dscal(ng, 1.0/xsum, gammaH, 1);
    cblas_dscal(nk, 1.0/xsum, kappaH, 1);
    cblas_dscal(ns, 1.0/xsum, sigmaH, 1);
    cblas_dscal(nt, 1.0/xsum, thetaH, 1);
    *locHist = locH;
    *magHist = magH;
    *betaHist = betaH;
    *gammaHist = gammaH;
    *kappaHist = kappaH;
    *sigmaHist = sigmaH;
    *thetaHist = thetaH;
    return 0;
}
//============================================================================//
static void getBaseAndExp(const double val, double *base, int *exp)
{
    char cval[64], cexp[64], temp[64];
    int i, k, l;
    bool lp1;
    memset(cval, 0, 64*sizeof(char));
    memset(cexp, 0, 64*sizeof(char));
    memset(temp, 0, 64*sizeof(char));
    sprintf(temp, "%04.2e", val);
    lp1 = true;
    k = 0;
    l = 0;
    for (i=0; i<strlen(temp); i++)
    {
        if (temp[i] == 'e')
        {
            lp1 = false;
            continue;
        }
        else
        {
            if (lp1)
            {
                cval[k] = temp[i];
                k = k + 1;
            }
            else
            {
                cexp[l] = temp[i];
                l = l + 1;
            }
        }
    }
    *exp = atoi(cexp);
    *base = atof(cval);
    return;
}

static void getBaseAndExpMT(const double *mtIn, double *mtOut, int *expOut)
{
    double xfact;
    int expWork, i;
    getBaseAndExp(mtIn[0], &mtOut[0], expOut);
    for (i=1; i<6; i++)
    {
        getBaseAndExp(mtIn[i], &mtOut[i], &expWork);
        if (expWork > *expOut){*expOut = expWork;}
    }
    // Rescale
    for (i=0; i<6; i++)
    {
        getBaseAndExp(mtIn[i], &mtOut[i], &expWork);
        xfact = pow(10.0, expWork - *expOut);
        mtOut[i] = mtOut[i]*xfact;
    }
    return;
}

int postmt_gmtHelper_makePsmecaLine(const enum compearthCoordSystem_enum basis,
                                    const double *mt,
                                    const double evla, const double evlo,
                                    const double evdp, const char *evid,
                                    char line[128])
{
    const char *fcnm = "postmt_gmtHelper_makePsmecaLine\0";
    double mtUSE[6], mtGMT[6];
    int exp, ierr;
    memset(line, 0, 128*sizeof(char));
    ierr = compearth_convertMT(1, basis, CE_USE, mt, mtUSE);
    if (ierr != 0)
    {
        printf("%s: Error switching basis\n", fcnm);
        return -1;
    }
    getBaseAndExpMT(mtUSE, mtGMT, &exp);
    sprintf(line, "%f %f %f %f %f %f %f %f %f %d %s\n",
            evlo, evla, evdp,
            mtGMT[0], mtGMT[1], mtGMT[2], mtGMT[3], mtGMT[4], mtGMT[5],
            exp, evid);
    return 0;
}

static void setFillColor(const int i, const int iopt, char color[32])
{
    memset(color, 0, 32*sizeof(char));
    if (i == iopt)
    {
        strcpy(color, "-Gyellow\0");
    }
    else
    {
        strcpy(color, "-Gred\0");
    }
    return;
}
//============================================================================//
/*
    sprintf("%7.2f %5.2f %f \n"<
            evlo, evla, evdp,
            mrr, mtt, mpp,

Columns: lon lat depth mrr mtt mpp mrt mrp mtp iexp name
-176.96 -29.25 48 7.68 0.09 -7.77 1.39 4.52 -3.26 26 X Y 010176A
*/


/* $Id$      */
/*--------------------------------------------------------------------*/
/*;  Copyright (C) 2008-2016                                          */
/*;  Associated Universities, Inc. Washington DC, USA.                */
/*;                                                                   */
/*;  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., 675 Massachusetts Ave, Cambridge,     */
/*;  MA 02139, USA.                                                   */
/*;                                                                   */
/*;Correspondence about this software should be addressed as follows: */
/*;         Internet email: bcotton@nrao.edu.                         */
/*;         Postal address: William Cotton                            */
/*;                         National Radio Astronomy Observatory      */
/*;                         520 Edgemont Road                         */
/*;                         Charlottesville, VA 22903-2475 USA        */
/*--------------------------------------------------------------------*/
#ifndef OBITSPECTRUMFIT_H
#define OBITSPECTRUMFIT_H

#include "Obit.h"
#include "ObitErr.h"
#include "ObitImage.h"
#include "ObitBeamShape.h"
#include "ObitThread.h"
#include "ObitInfoList.h"
#ifdef HAVE_GSL
#include <gsl/gsl_multifit_nlin.h>
#endif /* HAVE_GSL */

/*-------- Obit:  Merx mollis mortibus nuper ------------------*/
/**
 * \file ObitSpectrumFit.h
 *
 * ObitSpectrumFit Class for fitting spectra to image pixels
 *
 * This class does least squares fitting of log(s) as a polynomial in log($\nu$).
 * Either an image cube or a set of single plane images at arbitrary
 * frequencies may be fitted.
 * The result is an image cube of Log(S) with multiples of powers of log($\nu$)
 * as the planes.
 * The function ObitSpectrumFitEval will evaluate this fit and return an image
 * with the flux densities at the desired frequencies.
 *
 * \section ObitSpectrumFitaccess Creators and Destructors
 * An ObitSpectrumFit will usually be created using ObitSpectrumFitCreate which allows
 * specifying a name for the object as well as other information.
 *
 * A copy of a pointer to an ObitSpectrumFit should always be made using the
 * #ObitSpectrumFitRef function which updates the reference count in the object.
 * Then whenever freeing an ObitSpectrumFit or changing a pointer, the function
 * #ObitSpectrumFitUnref will decrement the reference count and destroy the object
 * when the reference count hits 0.
 * There is no explicit destructor.
 */

/*--------------Class definitions-------------------------------------*/
/** ObitSpectrumFit Class structure. */
typedef struct {
#include "ObitSpectrumFitDef.h"   /* this class definition */
} ObitSpectrumFit;

/*----------------- Macroes ---------------------------*/
/**
 * Macro to unreference (and possibly destroy) an ObitSpectrumFit
 * returns a ObitSpectrumFit*.
 * in = object to unreference
 */
#define ObitSpectrumFitUnref(in) ObitUnref (in)

/**
 * Macro to reference (update reference count) an ObitSpectrumFit.
 * returns a ObitSpectrumFit*.
 * in = object to reference
 */
#define ObitSpectrumFitRef(in) ObitRef (in)

/**
 * Macro to determine if an object is the member of this or a
 * derived class.
 * Returns TRUE if a member, else FALSE
 * in = object to reference
 */
#define ObitSpectrumFitIsA(in) ObitIsA (in, ObitSpectrumFitGetClass())

/*---------------Public functions---------------------------*/
/** Public: Class initializer. */
void ObitSpectrumFitClassInit (void);

/** Public: Default Constructor. */
ObitSpectrumFit* newObitSpectrumFit (gchar* name);

/** Public: Create/initialize ObitSpectrumFit structures */
ObitSpectrumFit* ObitSpectrumFitCreate (gchar* name, olong nterm);
/** Typedef for definition of class pointer structure */
typedef ObitSpectrumFit* (*ObitSpectrumFitCreateFP) (gchar* name,
						     olong nterm);

/** Public: ClassInfo pointer */
gconstpointer ObitSpectrumFitGetClass (void);

/** Public: Copy (deep) constructor. */
ObitSpectrumFit* ObitSpectrumFitCopy  (ObitSpectrumFit *in,
				       ObitSpectrumFit *out, ObitErr *err);

/** Public: Copy structure. */
void ObitSpectrumFitClone (ObitSpectrumFit *in, ObitSpectrumFit *out,
			   ObitErr *err);

/** Public: Fit spectrum to an image cube */
void ObitSpectrumFitCube (ObitSpectrumFit* in, ObitImage *inImage,
			  ObitImage *outImage, ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef void(*ObitSpectrumFitCubeFP) (ObitSpectrumFit* in, ObitImage *inImage,
				      ObitImage *outImage, ObitErr *err);

/** Public: Fit spectrum to an array of images */
void ObitSpectrumFitImArr (ObitSpectrumFit* in, olong nimage, ObitImage **imArr,
			   ObitImage *outImage, ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef void(*ObitSpectrumFitImArrFP) (ObitSpectrumFit* in, olong nimage, ObitImage **imArr,
			   ObitImage *outImage, ObitErr *err);

/* Do actual fitting */
void ObitSpectrumFitter (ObitSpectrumFit* in, ObitErr *err);
typedef void(*ObitSpectrumFitterFP) (ObitSpectrumFit* in, ObitErr *err);

/** Public: Evaluate spectrum */
void ObitSpectrumFitEval (ObitSpectrumFit* in, ObitImage *inImage,
			  odouble outFreq, ObitImage *outImage, ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef void(*ObitSpectrumFitEvalFP) (ObitSpectrumFit* in, ObitImage *inImage,
				      odouble outFreq, ObitImage *outImage,
				      ObitErr *err);
/** Private: Write output image */
void ObitSpectrumWriteOutput (ObitSpectrumFit* in, ObitImage *outImage,
			      ObitErr *err);
typedef void (*ObitSpectrumWriteOutputFP) (ObitSpectrumFit* in, ObitImage *outImage,
					   ObitErr *err);
/** Public: Fit single spectrum */
ofloat* ObitSpectrumFitSingle (olong nfreq, olong nterm, odouble refFreq, odouble *freq,
			       ofloat *flux, ofloat *sigma, gboolean doBrokePow,
			       ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef ofloat*(*ObitSpectrumFitSingleFP) (olong nfreq, olong nterm, odouble refFreq,
					   odouble *freq, ofloat *flux, ofloat *sigma,
					   gboolean doBrokePow, ObitErr *err);

/** Public: Make fitting arg structure */
gpointer ObitSpectrumFitMakeArg (olong nfreq, olong nterm,
				 odouble refFreq, odouble *freq,
				 gboolean doBrokePow,
				 ofloat **out, ObitErr *err);

/** Public: Fit single spectrum using arg */
void ObitSpectrumFitSingleArg (gpointer arg, ofloat *flux, ofloat  *sigma,
			       ofloat *out);

/** Public: Kill fitting arg structure */
void ObitSpectrumFitKillArg (gpointer arg);
/*----------- ClassInfo Structure -----------------------------------*/
/**
 * ClassInfo Structure.
 * Contains class name, a pointer to any parent class
 * (NULL if none) and function pointers.
 */
typedef struct  {
#include "ObitSpectrumFitClassDef.h"
} ObitSpectrumFitClassInfo;

#endif /* OBITFSPECTRUMFIT_H */


#include <math.h>
#include <stdlib.h>
#if !defined(__APPLE__)
#include <malloc.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>

#include <fftw3.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_sf_erf.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_legendre.h>
#include <gsl/gsl_sf_bessel.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_sf_expint.h>
#include <gsl/gsl_deriv.h>
#include <gsl/gsl_interp2d.h>
#include <gsl/gsl_spline2d.h>

#include "../cosmolike_core/theory/basics.c"
#include "../cosmolike_core/theory/structs.c"
#include "../cosmolike_core/theory/parameters.c"
#include "../cosmolike_core/emu17/P_cb/emu.c"
#include "../cosmolike_core/theory/recompute.c"
#include "../cosmolike_core/theory/cosmo3D.c"
#include "../cosmolike_core/theory/redshift_spline.c"
#include "../cosmolike_core/theory/halo.c"
#include "../cosmolike_core/theory/HOD.c"
#include "../cosmolike_core/theory/pt.c"
#include "../cosmolike_core/theory/cosmo2D_fourier.c"
#include "../cosmolike_core/theory/IA.c"
#include "../cosmolike_core/theory/BAO.c"
#include "../cosmolike_core/theory/external_prior.c"
#include "../cosmolike_core/theory/covariances_3D.c"
#include "../cosmolike_core/theory/covariances_fourier.c"
#include "../cosmolike_core/theory/CMBxLSS_fourier.c"
#include "../cosmolike_core/theory/covariances_CMBxLSS_fourier.c"

#include "../cosmolike_core/theory/covariances_binned_simple.c"
#include "../cosmolike_core/theory/run_covariances_fourier_binned_6x2pt.c"

#include "init_LSSxCMB.c"

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

  int i,l,m,n,o,s,p,nl1,t,k;
  char OUTFILE[400],filename[400],arg1[400],arg2[400];

  int N_scenarios=2;
  double area_table[2]={12300.0,16500.0}; // Y1 corresponds to DESC SRD Y1, Y6 corresponds to assuming that we cover the full SO area=0.4*fsky and at a depth of 26.1 which is in a range of reasonable scenarios (see https://github.com/LSSTDESC/ObsStrat/tree/static/static )
  double nsource_table[2]={11.0,23.0};
  double nlens_table[2]={18.0,41.0};

  char survey_designation[2][200]={"LSSTxSO_Y1","LSSTxSO_Y6"};

  char source_zfile[2][400]={"src_LSSTY1","src_LSSTY6"};

#ifdef ONESAMPLE
  char lens_zfile[2][400]={"src_LSSTY1","src_LSSTY6"};
  nlens_table[0] = nsource_table[0];
  nlens_table[1] = nsource_table[1];
#else
  char lens_zfile[2][400]={"lens_LSSTY1","lens_LSSTY6"};
#endif

  int hit=atoi(argv[1]);
  Ntable.N_a=100;
  k=1;

  t = atoi(argv[2]);

  //RUN MODE setup
  init_cosmo_runmode("emu");
  // init_binning_fourier(20,30.0,3000.0,3000.0,21.0,10,10);
  init_binning_fourier(15,20.0,3000.0,3000.0,0.0,10,10);
  init_survey(survey_designation[t],nsource_table[t],nlens_table[t],area_table[t]);
  sprintf(arg1,"zdistris/%s",source_zfile[t]);
  sprintf(arg2,"zdistris/%s",lens_zfile[t]);
  init_galaxies(arg1,arg2,"none","none","source_std","LSST_gold");
  init_IA("none","GAMA");
  init_probes("6x2pt");

  if(t==0) init_cmb("so_Y1");
  if(t==1) init_cmb("so_Y5");
  cmb.fsky = survey.area*survey.area_conversion_factor/(4.*M_PI);
  //set l-bins for shear, ggl, clustering, clusterWL

  double lmin=like.lmin;
  double lmax=like.lmax;
  int Nell=like.Ncl;
  int Ncl = Nell;
  double logdl=(log(lmax)-log(lmin))/Nell;
  double *ellmin, *dell;
  ellmin=create_double_vector(0,Nell);
  dell=create_double_vector(0,Nell-1);
  double ellmax;
  for(i=0; i<Nell ; i++){
    ellmin[i]=exp(log(lmin)+(i+0.0)*logdl);
    ellmax = exp(log(lmin)+(i+1.0)*logdl);
    dell[i]=ellmax-ellmin[i];
  }
  ellmin[Nell] = ellmax;
  like.ell = ellmin;

  covparams.ng = 1;
  covparams.cng= 0;

  printf("----------------------------------\n");
  sprintf(survey.name,"%s_area%le_ng%le_nl%le",survey_designation[t],survey.area,survey.n_gal,survey.n_lens);
  printf("area: %le n_source: %le n_lens: %le\n",survey.area,survey.n_gal,survey.n_lens);

  // sprintf(covparams.outdir,"/home/u17/timeifler/covparallel/");
#ifdef ONESAMPLE
  sprintf(covparams.outdir,"out_cov_lsstxso_1sample/");
#else
  sprintf(covparams.outdir,"out_cov_lsstxso/");
  //sprintf(covparams.outdir,"/halo_nobackup/cosmos/teifler/covparallel/");
#endif

  printf("----------------------------------\n");
  if (like.shear_shear)
  {
    sprintf(OUTFILE,"%s_ssss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);

    for (l=0;l<tomo.shear_Npowerspectra; l++){
      for (m=l;m<tomo.shear_Npowerspectra; m++){
        if(k==hit){
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_shear_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }

        k=k+1;
      }
    }
  }
  if (like.pos_pos)
  {
    sprintf(OUTFILE,"%s_llll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Npowerspectra; l++){
      for (m=l;m<tomo.clustering_Npowerspectra; m++){
        if(k==hit){
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.shear_pos)
  {
    sprintf(OUTFILE,"%s_lsls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.ggl_Npowerspectra; l++){
      for (m=l;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.shear_pos && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_lsss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.ggl_Npowerspectra; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ggl_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.pos_pos && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_llss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Npowerspectra; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_clustering_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.pos_pos && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_llls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Npowerspectra; l++){
      for (m=0;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_clustering_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_lkss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_ksss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.shear_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_lkls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=0;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_ksls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.ggl_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk && like.pos_pos)
  {
    sprintf(OUTFILE,"%s_lkll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=0;m<tomo.clustering_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.pos_pos)
  {
    sprintf(OUTFILE,"%s_ksll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.clustering_Npowerspectra; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.gk)
  {
    sprintf(OUTFILE,"%s_lklk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.clustering_Nbin; l++){
      for (m=l;m<tomo.clustering_Nbin; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_gk_gk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks && like.gk)
  {
    sprintf(OUTFILE,"%s_kslk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=0;m<tomo.clustering_Nbin; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_gk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  if (like.ks)
  {
    sprintf(OUTFILE,"%s_ksks_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (l=0;l<tomo.shear_Nbin; l++){
      for (m=l;m<tomo.shear_Nbin; m++){
        if(k==hit) {
          sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
          // if (fopen(filename, "r") != NULL){exit(1);}
          run_cov_ks_ks_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,l,m,k);
        }
        k=k+1;
      }
    }
  }
  ////////

  if (like.kk && like.shear_shear)
  {
    sprintf(OUTFILE,"%s_kkss_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.shear_Npowerspectra; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_shear_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.shear_pos)
  {
    sprintf(OUTFILE,"%s_kkls_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.ggl_Npowerspectra; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_ggl_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.pos_pos)
  {
    sprintf(OUTFILE,"%s_kkll_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.clustering_Npowerspectra; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_clustering_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.gk)
  {
    sprintf(OUTFILE,"%s_kklk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.clustering_Nbin; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_gk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk && like.ks)
  {
    sprintf(OUTFILE,"%s_kkks_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    for (m=0;m<tomo.shear_Nbin; m++){
      if(k==hit) {
        sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
        // if (fopen(filename, "r") != NULL){exit(1);}
        run_cov_kk_ks_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,m,k);
      }
      k=k+1;
    }
  }
  if (like.kk)
  {
    sprintf(OUTFILE,"%s_kkkk_cov_Ncl%d_Ntomo%d",survey.name,Ncl,tomo.shear_Nbin);
    if(k==hit) {
      sprintf(filename,"%s%s_%d",covparams.outdir,OUTFILE,k);
      // if (fopen(filename, "r") != NULL){exit(1);}
      run_cov_kk_kk_fourier_bin(OUTFILE,covparams.outdir,ellmin,Ncl,k);
    }
    k=k+1;
  }

  printf("number of cov blocks for parallelization: %d\n",k-1);
  printf("-----------------\n");
  printf("PROGRAM EXECUTED\n");
  printf("-----------------\n");
  return 0;
}


// Copyright (C) 2022. Huawei Technologies Co., Ltd. All rights reserved.

// 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.

#ifndef COMMON_H
#define COMMON_H

#include <cmath>
#include <optional>
#include <yato/array_view.h>

//#define CHECK_ASSERT

#include <training/system/Errors.h>
#include <training/system/Name.h>
#include <training/system/TypeHalf.h>
#include <training/system/Types.h>

#include <map>
#include <utility>

#if defined(_BLAS) && !defined(_BLAS_ENHANCE)
extern "C"
{
#include <cblas.h>
}
#else
#ifndef OPENBLAS_CONST
#define OPENBLAS_CONST const
#endif

typedef enum CBLAS_TRANSPOSE
{
    CblasNoTrans = 111,
    CblasTrans = 112,
    CblasConjTrans = 113,
    CblasConjNoTrans = 114
} CBLAS_TRANSPOSE;

typedef enum CBLAS_UPLO
{
    CblasUpper = 121,
    CblasLower = 122
} CBLAS_UPLO;
#endif

#ifdef CHECK_ASSERT
#define CHECK_NEAR ASSERT_NEAR
#else
#define CHECK_NEAR EXPECT_NEAR
#endif

#define RAUL_E 2.71828182845904523536        // e
#define RAUL_LOG2E 1.44269504088896340736    // log2(e)
#define RAUL_LOG10E 0.434294481903251827651  // log10(e)
#define RAUL_LN2 0.693147180559945309417     // ln(2)
#define RAUL_LN10 2.30258509299404568402     // ln(10)
#define RAUL_PI 3.14159265358979323846       // pi
#define RAUL_PI_2 1.57079632679489661923     // pi/2
#define RAUL_PI_4 0.785398163397448309616    // pi/4
#define RAUL_1_PI 0.318309886183790671538    // 1/pi
#define RAUL_2_PI 0.636619772367581343076    // 2/pi
#define RAUL_2_SQRTPI 1.12837916709551257390 // 2/sqrt(pi)
#define RAUL_SQRT2_PI 0.79788456080286535588 // sqrt(2/pi)
#define RAUL_SQRT2 1.41421356237309504880    // sqrt(2)
#define RAUL_SQRT1_2 0.707106781186547524401 // 1/sqrt(2)

#define GELU_CONST 0.044715

namespace raul
{

enum class Limit : int
{
    Left = 0,
    Middle = 1,
    Right = 2
};

enum class Dimension : int
{
    Default = -1,
    Batch = 0,
    Depth = 1,
    Height = 2,
    Width = 3
};

#if defined(_MSC_VER)
#define INLINE __forceinline
#else
#define INLINE __attribute__((always_inline))
#endif

template<typename Type>
class TensorImpl;
typedef TensorImpl<dtype> Tensor;
typedef TensorImpl<half> TensorFP16;

#if defined(ANDROID)
#define TOMMTYPE(var) static_cast<typename MM::type>(var)
#else
#define TOMMTYPE(var) castHelper<typename MM::type>::cast(var)
#endif

using shape = yato::dimensionality<4U, size_t>;
} // raul namespace

namespace raul
{

enum class NetworkMode
{
    Train = 0,
    Test = 1,
    TrainCheckpointed = 2
};

enum class CompressionMode
{
    NONE = -1,
    FP16 = 0,
    INT8 = 1
};

enum class CalculationMode
{
    DETERMINISTIC = 0,
#if defined(_OPENMP)
    FAST = 1,
#endif
};

/**
 * @brief Hardware target platform
 *
 */
enum class ExecutionTarget
{
    CPU = 0,
    CPUFP16 = 1
};

/**
 * @brief Hardware target platform per layer
 *
 * \note Might override execution target for workflow, useful for mixed precision
 */
enum class LayerExecutionTarget
{
    Default = -1, // use same as ExecutionTarget
    CPU = 0,      // from this point enums should be aligned with ExecutionTarget (due to LayerExecutionTarget = static_cast<ExecutionTarget>(enum))
    CPUFP16 = 1
};

/**
 * @brief Memory allocation mode
 */
enum class AllocationMode
{
    STANDARD,
    POOL
};

enum class DeclarationType
{
    Tensor = 0,
    Shape = 1,
    //    Alias = 2
};

class OpenclInitializer;

class Common
{
  public:

    // generate vector of random index permutation of [0..n-1]
    static void generate_permutation(size_t n, std::vector<size_t>& ind_vector, unsigned int seed = 0);

    /*
     * [cols x rows]
     * A[k x m]
     * B[n x k]
     * C[n x m]
     * https://software.intel.com/en-us/mkl-developer-reference-c-cblas-gemm
     * C = alpha * A * B + beta * C
     * bOffset - in elements (not bytes)
     */
    static void gemm(OPENBLAS_CONST CBLAS_TRANSPOSE transA,
                     OPENBLAS_CONST CBLAS_TRANSPOSE transB,
                     size_t m,
                     size_t n,
                     size_t k,
                     OPENBLAS_CONST dtype alpha,
                     OPENBLAS_CONST dtype* a,
                     OPENBLAS_CONST dtype* b,
                     OPENBLAS_CONST dtype beta,
                     dtype* c);

    static void gemm(OPENBLAS_CONST CBLAS_TRANSPOSE transA,
                     OPENBLAS_CONST CBLAS_TRANSPOSE transB,
                     size_t m,
                     size_t n,
                     size_t k,
                     OPENBLAS_CONST dtype alpha,
                     OPENBLAS_CONST half* a,
                     OPENBLAS_CONST half* b,
                     OPENBLAS_CONST dtype beta,
                     half* c);

    /**
     * @brief : Basic Linear Algebra Subroutine y = y + ax
     *
     *  \f[
     *      \vec{y} = \vec{y} + \alpha * \vec{x},
     *  \f]
     *
     * @param n The number of elements in vectors x and y.
     * @param sa The scalar alpha.
     * @param sx The vector x of length n. Specified as: a one-dimensional array of (at least) length \f$ 1+(n-1)|incx| \f$.
     * @param incx The stride for vector x. Specified as: an integer. It can have any value.
     * @param sy The vector y of length n. Specified as: a one-dimensional array of (at least) length \f$ 1+(n-1)|incy| \f$.
     * @param incy The stride for vector y.
     * @param xOffset The offset for vector x.
     * @param yOffset The offset for vector y.
     * @return The vector y, containing the results of the computation.
     */
    static void axpy(size_t n, OPENBLAS_CONST dtype sa, OPENBLAS_CONST dtype* sx, size_t incx, dtype* sy, size_t incy, size_t xOffset = 0, size_t yOffset = 0);
    static void axpy(size_t n, OPENBLAS_CONST dtype sa, OPENBLAS_CONST half* sx, size_t incx, half* sy, size_t incy, size_t xOffset = 0, size_t yOffset = 0);

    /**
     * @brief : Basic Linear Algebra Subroutine y = ax + by
     *
     *  \f[
     *      \vec{y} = \alpha \vec{x} + \beta \vec{y},
     *  \f]
     *
     * @param n The number of elements in vectors x and y.
     * @param alpha The scalar alpha.
     * @param x The vector x of length n. Specified as: a one-dimensional array of (at least) length \f$ 1+(n-1)|incx| \f$.
     * @param incx The stride for vector x. Specified as: an integer. It can have any value.
     * @param beta The scalar beta.
     * @param y The vector y of length n. Specified as: a one-dimensional array of (at least) length \f$ 1+(n-1)|incy| \f$.
     * @param incy The stride for vector y.
     * @param xOffset The offset for vector x.
     * @param yOffset The offset for vector y.
     * @return The vector y, containing the results of the computation.
     */
    static int axpby(OPENBLAS_CONST size_t n,
                     OPENBLAS_CONST dtype alpha,
                     OPENBLAS_CONST dtype* x,
                     OPENBLAS_CONST size_t incx,
                     OPENBLAS_CONST dtype beta,
                     dtype* y,
                     OPENBLAS_CONST size_t incy,
                     size_t xOffset,
                     size_t yOffset);

    static int axpby(OPENBLAS_CONST size_t n,
                     OPENBLAS_CONST dtype alpha,
                     OPENBLAS_CONST half* x,
                     OPENBLAS_CONST size_t incx,
                     OPENBLAS_CONST dtype beta,
                     half* y,
                     OPENBLAS_CONST size_t incy,
                     size_t xOffset,
                     size_t yOffset);
    /**
     * @brief : Basic Linear Algebra Subroutine y = alpha * a * x + beta * y
     *
     * Vector by vector element wise multiplication
     *
     *  \f[
     *      \vec{y} = \alpha \vec{a} \vec{x} + \beta \vec{y},
     *  \f]
     *
     * @param n The number of elements in vectors x and y.
     * @param alpha The scalar alpha.
     * @param a The vector of length n.
     * @param x The vector x of length n. Specified as: a one-dimensional array of (at least) length \f$ 1+(n-1)|incx| \f$.
     * @param incx The stride for vector x. Specified as: an integer. It can have any value.
     * @param beta The scalar beta.
     * @param y The vector y of length n. Specified as: a one-dimensional array of (at least) length \f$ 1+(n-1)|incy| \f$.
     * @param incy The stride for vector y.
     */

    static void hadamard(OPENBLAS_CONST size_t n,
                         OPENBLAS_CONST dtype alpha,
                         OPENBLAS_CONST dtype* a,
                         OPENBLAS_CONST dtype* x,
                         OPENBLAS_CONST size_t incx,
                         OPENBLAS_CONST dtype beta,
                         dtype* y,
                         OPENBLAS_CONST size_t incy);

    static dtype dot(size_t n, OPENBLAS_CONST dtype* sx, size_t incx, OPENBLAS_CONST dtype* sy, size_t incy);

    static void scal(size_t n, OPENBLAS_CONST dtype sa, dtype* sx, size_t incx);

    static void transpose(Tensor& tensor, size_t cols);
    static void transpose(TensorFP16& tensor, size_t cols);

    /*
     * memory for dst should be allocated externaly
     */
    static void addPadding1D(const dtype* src, dtype* dst, size_t srcChannels, size_t srcSize, size_t dstSize, bool reversedOrder = false);
    template<typename T>
    static void addPadding2D(const T* src, T* dst, size_t srcChannels, size_t srcWidth, size_t srcHeight, size_t dstWidth, size_t dstHeight)
    {
        if ((dstWidth >= srcWidth) && (dstHeight >= srcHeight))
        {
            size_t padWidth = dstWidth - srcWidth;
            size_t padHeight = dstHeight - srcHeight;

            size_t leftPad = padWidth / 2;
            // size_t rightPad = padWidth - leftPad;
            size_t topPad = padHeight / 2;
            size_t bottomPad = padHeight - topPad;

            for (size_t d = 0; d < srcChannels; ++d)
            {
                // top
                for (size_t y = 0; y < topPad; ++y)
                {
                    for (size_t x = 0; x < dstWidth; ++x)
                    {
                        dst[d * dstWidth * dstHeight + dstWidth * y + x] = static_cast<T>(0.0_dt);
                    }
                }

                for (size_t y = topPad; y < topPad + srcHeight; ++y)
                {
                    // left
                    for (size_t x = 0; x < leftPad; ++x)
                    {
                        dst[d * dstWidth * dstHeight + dstWidth * y + x] = static_cast<T>(0.0_dt);
                    }

                    // src
                    for (size_t x = leftPad; x < leftPad + srcWidth; ++x)
                    {
                        dst[d * dstWidth * dstHeight + dstWidth * y + x] = src[d * srcWidth * srcHeight + srcWidth * (y - topPad) + x - leftPad];
                    }

                    // right
                    for (size_t x = leftPad + srcWidth; x < dstWidth; ++x)
                    {
                        dst[d * dstWidth * dstHeight + dstWidth * y + x] = static_cast<T>(0.0_dt);
                    }
                }

                // bottom
                for (size_t y = dstHeight - bottomPad; y < dstHeight; ++y)
                {
                    for (size_t x = 0; x < dstWidth; ++x)
                    {
                        dst[d * dstWidth * dstHeight + dstWidth * y + x] = static_cast<T>(0.0_dt);
                    }
                }
            }
        }
    }

    /*
     * memory for dst should be allocated externaly
     */
    static void removePadding1D(const dtype* src, dtype* dst, size_t srcChannels, size_t srcSize, size_t dstSize, bool reversedOrder = false, bool overwrite = true);
    template<typename T>
    static void removePadding2D(const T* src, T* dst, size_t srcChannels, size_t srcWidth, size_t srcHeight, size_t dstWidth, size_t dstHeight, bool overwrite = true)
    {
        if ((dstWidth <= srcWidth) && (dstHeight <= srcHeight))
        {
            size_t padWidth = srcWidth - dstWidth;
            size_t padHeight = srcHeight - dstHeight;

            size_t leftPad = padWidth / 2;
            // size_t rightPad = padWidth - leftPad;
            size_t topPad = padHeight / 2;
            // size_t bottomPad = padHeight - topPad;

            if (overwrite)
            {
                for (size_t d = 0; d < srcChannels; ++d)
                {
                    for (size_t y = 0; y < dstHeight; ++y)
                    {
                        for (size_t x = 0; x < dstWidth; ++x)
                        {
                            dst[d * dstWidth * dstHeight + dstWidth * y + x] = src[d * srcWidth * srcHeight + srcWidth * (y + topPad) + x + leftPad];
                        }
                    }
                }
            }
            else
            {
                for (size_t d = 0; d < srcChannels; ++d)
                {
                    for (size_t y = 0; y < dstHeight; ++y)
                    {
                        for (size_t x = 0; x < dstWidth; ++x)
                        {
                            dst[d * dstWidth * dstHeight + dstWidth * y + x] += src[d * srcWidth * srcHeight + srcWidth * (y + topPad) + x + leftPad];
                        }
                    }
                }
            }
        }
    }

    /*
     * paddingWidth, paddingHeight - zero padding added for both sides of the input
     * memory for matrix should be allocated externaly
     */
    template<typename T>
    static void im2col(const T* image,
                       size_t imageWidth,
                       size_t imageHeight,
                       size_t imageChannels,
                       size_t filterWidth,
                       size_t filterHeight,
                       size_t strideWidth,
                       size_t strideHeight,
                       size_t paddingWidth,
                       size_t paddingHeight,
                       T* matrix,
                       bool reversedOrder = false);

    static size_t im2colOutputSize(size_t imageWidth,
                                   size_t imageHeight,
                                   size_t imageChannels,
                                   size_t filterWidth,
                                   size_t filterHeight,
                                   size_t strideWidth,
                                   size_t strideHeight,
                                   size_t paddingWidth,
                                   size_t paddingHeight,
                                   size_t dilationWidth,
                                   size_t dilationHeight);

    /*
     * paddingWidth, paddingHeight - zero padding added for both sides of the input
     * memory for image should be allocated externaly
     */
    template<typename T>
    static void col2im(const T* matrix,
                       size_t imageWidth,
                       size_t imageHeight,
                       size_t imageChannels,
                       size_t filterWidth,
                       size_t filterHeight,
                       size_t strideWidth,
                       size_t strideHeight,
                       size_t paddingWidth,
                       size_t paddingHeight,
                       T* image,
                       bool reversedOrder = false,
                       bool zeroOutput = true);

    /*
     * Rectified Linear Unit
     */
    template<typename T>
    static T ReLU(T x)
    {
        return std::max(static_cast<T>(0), x);
    }
    template<typename T>
    static T ReLU6(T x)
    {
        return std::min(std::max(static_cast<T>(0), x), static_cast<T>(6.0_dt));
    }

    template<typename T>
    static void ReLU(const T& in, T& out)
    {
        std::transform(in.begin(), in.end(), out.begin(), [&](typename T::type val) -> typename T::type { return ReLU(val); });
    }

    template<typename T>
    static void ReLU6(const T& in, T& out)
    {
        std::transform(in.begin(), in.end(), out.begin(), [&](typename T::type val) -> typename T::type { return ReLU6(val); });
    }

    template<typename T>
    static void ReLUBackward(const T& out, const T& delta, T& prevDelta)
    {
#if defined(_OPENMP)
#pragma omp parallel for
#endif
        for (size_t q = 0; q < prevDelta.size(); ++q)
        {
            prevDelta[q] += (out[q] > static_cast<typename T::type>(0)) ? delta[q] : static_cast<typename T::type>(0);
        }
    }

    template<typename T>
    static void ReLU6Backward(const T& out, const T& delta, T& prevDelta)
    {
#if defined(_OPENMP)
#pragma omp parallel for
#endif
        for (size_t q = 0; q < prevDelta.size(); ++q)
        {
            prevDelta[q] += (out[q] > static_cast<typename T::type>(0) && out[q] < static_cast<typename T::type>(6.0f)) ? delta[q] : static_cast<typename T::type>(0);
        }
    }

    /*
     * Gaussian error linear unit
     * @see https://arxiv.org/abs/1606.08415
     */
    static dtype GeLU_Erf(dtype x);
    static dtype GeLU_Tanh(dtype x);

    /*
     * Hard Sigmoid
     */
    template<typename T>
    static T HSigmoid(T x)
    {
        return static_cast<T>(ReLU6(TODTYPE(x) + 3.0_dt) / 6.0_dt);
    }

    /*
     * Hard Swish
     */
    template<typename T>
    static T HSwish(T x)
    {
        return x * HSigmoid(x);
    }
    static dtype sign(dtype x) { return TODTYPE((0.0_dt < x) - (x < 0.0_dt)); }

    template<typename T, typename U>
    static void copyView(const T& view_from, U& view_to, const bool overwrite = false)
    {
        auto retLhs = [](typename T::value_type& lhs, [[maybe_unused]] typename T::value_type& rhs) { return lhs; };

        auto copyViewImpl = [](const T& view_from, U& view_to, auto&& func)
        {
            for (size_t i1 = 0; i1 < view_from.size(0); ++i1)
            {
                for (size_t i2 = 0; i2 < view_from.size(1); ++i2)
                {
                    for (size_t i3 = 0; i3 < view_from.size(2); ++i3)
                    {
                        for (size_t i4 = 0; i4 < view_from.size(3); ++i4)
                        {
                            view_to[i1][i2][i3][i4] = func(view_from[i1][i2][i3][i4], view_to[i1][i2][i3][i4]);
                        }
                    }
                }
            }
        };

        if (overwrite)
        {
            copyViewImpl(view_from, view_to, retLhs);
        }
        else
        {
            copyViewImpl(view_from, view_to, std::plus<typename T::value_type>());
        }
    }

    template<typename T>
    static void unpack4D(const T& src, T& dst, Dimension dir, size_t index, const Name& layerType, const Name& layerName, bool overwrite)
    {
        auto input4d = src.get4DView();
        auto inputDims = yato::dims(src.getDepth(), src.getHeight(), src.getWidth());

        auto outputDims = dst.getShape();

        const typename T::type* startEl = nullptr;
        switch (dir)
        {
            case Dimension::Depth:
                startEl = &input4d[0][index][0][0];
                break;
            case Dimension::Height:
                startEl = &input4d[0][0][index][0];
                break;
            default:
                throw std::runtime_error(layerType + "[" + layerName + "]: unpack4D unknown dim");
        }

        auto srcView = yato::array_view_4d<const typename T::type>(startEl, outputDims, inputDims);
        auto outputView = dst.get4DView();
        Common::copyView(srcView, outputView, overwrite);
    }

    template<typename T>
    static void pack4D(const T& src, T& dst, Dimension dir, size_t index, const Name& layerType, const Name& layerName, bool overwrite)
    {
        auto output4d = dst.get4DView();

        yato::dimensionality<3U, size_t> concatDims(dst.getDepth(), dst.getHeight(), dst.getWidth());

        auto srcView = src.get4DView();
        typename T::type* startEl = nullptr;
        switch (dir)
        {
            case Dimension::Depth:
                startEl = &output4d[0][index][0][0];
                break;
            case Dimension::Height:
                startEl = &output4d[0][0][index][0];
                break;
            default:
                throw std::runtime_error(layerType + "[" + layerName + "]: pack4D unknown dim");
        }

        auto dstView = yato::array_view_4d<typename T::type>(startEl, src.getShape(), concatDims);
        Common::copyView(srcView, dstView, overwrite);
    }

    /*
     * Upper triangle of a rectangular array
     */
    template<typename T>
    static void triu(T* data, size_t nrows, size_t ncols, int diag = 0)
    {
        size_t i = 0;
        int cols = (int)ncols;
        int rows = (int)nrows;
        for (int r = 0; r < rows; ++r)
        {
            for (int c = 0; c < cols; ++c, ++i)
            {
                if (c - r - diag < 0)
                {
                    data[i] = static_cast<T>(0);
                }
            }
        }
    }

    /*
     * Applies a 1D convolution over an input signal composed of several input planes.
     * Supports 2 modes:
     *  1. PyTorch style: Input[N, C, 1, L1] (or [N, 1, C, L1]) -> Output[N, FILTERS, 1, L2] (or [N, 1, FILTERS, L2])
     *  2. TensorFlow style: Input[N, L1, 1, C] (or [N, 1, L1, C]) -> Output[N, L2, 1, FILTERS] (or [N, 1, L2, FILTERS])
     * Output is not zeroed prior to convolution (operator += is used)
     */
    static void conv1d(const dtype* input,
                       dtype* output,
                       const dtype* kernel,
                       const dtype* bias,
                       size_t batchSize,
                       size_t inputSize,
                       size_t inputChannels,
                       size_t outputSize,
                       size_t outputChannels,
                       size_t kernelSize,
                       size_t padding,
                       size_t stride,
                       size_t dilation = 1U,
                       size_t groups = 1U,
                       bool tfStyle = false);

    /*
     * Applies 2D convolution over input tensor, all channels convolved
     * Output is not zeroed prior to convolution (operator += is used)
     */
    template<typename T>
    static void conv2d(const T* input,
                       T* output,
                       const T* kernel,
                       const T* bias,
                       size_t batchSize,
                       size_t inputWidth,
                       size_t inputHeight,
                       size_t inputChannels,
                       size_t outputWidth,
                       size_t outputHeight,
                       size_t outputChannels,
                       size_t kernelWidth,
                       size_t kernelHeight,
                       size_t paddingW,
                       size_t paddingH,
                       size_t strideW,
                       size_t strideH,
                       size_t dilationW = 1U,
                       size_t dilationH = 1U,
                       size_t groups = 1U)
    {
        auto inputs3D = yato::array_view_3d<T>(const_cast<T*>(input), yato::dims(batchSize, inputChannels, inputHeight * inputWidth));
        auto outputs3D = yato::array_view_3d<T>(output, yato::dims(batchSize, outputChannels, outputHeight * outputWidth));
        auto kernelsWeights4D = yato::array_view_4d<T>(const_cast<T*>(kernel), yato::dims(outputChannels, inputChannels / groups, kernelHeight, kernelWidth));

        for (size_t q = 0; q < batchSize; ++q)
        {
            for (size_t d = 0; d < outputChannels; ++d)
            {
                std::fill(outputs3D[q][d].begin(), outputs3D[q][d].end(), static_cast<T>(0.0_dt));
            }

            size_t inputWidthPadded = inputWidth + 2 * paddingW;
            size_t inputHeightPadded = inputHeight + 2 * paddingH;

            std::vector<T> inputPadded(inputChannels * inputHeightPadded * inputWidthPadded);

            Common::addPadding2D(&inputs3D[q][0][0], inputPadded.data(), inputChannels, inputWidth, inputHeight, inputWidthPadded, inputHeightPadded);

            auto inputPadded2D = yato::view(inputPadded).reshape(yato::dims(inputChannels, inputHeightPadded * inputWidthPadded));

            for (size_t group = 0; group < groups; ++group)
            {
                for (size_t kernelIndex = 0; kernelIndex < outputChannels / groups; ++kernelIndex)
                {
                    for (size_t d = 0; d < inputChannels / groups; ++d)
                    {
                        for (size_t oy = 0; oy < outputHeight; ++oy)
                        {
                            for (size_t ox = 0; ox < outputWidth; ++ox)
                            {
                                for (size_t ky = 0; ky < kernelHeight; ++ky)
                                {
                                    for (size_t kx = 0; kx < kernelWidth; ++kx)
                                    {
                                        outputs3D[q][kernelIndex + group * outputChannels / groups][oy * outputWidth + ox] +=
                                            kernelsWeights4D[kernelIndex + group * outputChannels / groups][d][ky][kx] *
                                            inputPadded2D[d + group * inputChannels / groups][oy * inputWidthPadded * strideH + ky * dilationH * inputWidthPadded + ox * strideW + kx * dilationW];
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        if (bias)
        {
            for (size_t q = 0; q < batchSize; ++q)
            {
                for (size_t kernelIndex = 0; kernelIndex < outputChannels; ++kernelIndex)
                {
                    for (size_t oy = 0; oy < outputHeight; ++oy)
                    {
                        for (size_t ox = 0; ox < outputWidth; ++ox)
                        {
                            outputs3D[q][kernelIndex][oy * outputWidth + ox] += bias[kernelIndex];
                        }
                    }
                }
            }
        }
    }

    template<typename T = dtype, typename Iterator>
    static void arange(Iterator begin, Iterator end, T start = static_cast<T>(0), T step = static_cast<T>(1))
    {
        auto val = start;
        for (auto p = begin; p != end; ++p)
        {
            *p = static_cast<std::remove_reference_t<decltype(*p)>>(val);
            val += step;
        }
    }

    template<typename T = dtype, typename Iterable>
    static void arange(Iterable& i, T start = static_cast<T>(0), T step = static_cast<T>(1))
    {
        return arange(i.begin(), i.end(), start, step);
    }

    static void replaceAll(std::string& str, const std::string& srcSubstr, const std::string& tgtSubstr)
    {
        size_t start_pos = 0;
        while ((start_pos = str.find(srcSubstr, start_pos)) != std::string::npos)
        {
            str.replace(start_pos, srcSubstr.length(), tgtSubstr);
            start_pos += tgtSubstr.length(); // srcSubstr could be a substring of tgtSubstr
        }
    }

    static bool startsWith(const std::string& str, const std::string& srcSubstr) { return (str.rfind(srcSubstr, 0) == 0); }

    static std::vector<std::string> split(const std::string& string, char delimeter);

    /*
     * @see https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html
     */
    template<typename T>
    static bool shapeIsBroadcastable(const T& from, const T& to)
    {
        const auto n = to.dimensions_num();
        for (size_t i = 0; i < n; ++i)
        {
            if (from[i] != to[i] && from[i] != 1U && to[i] != 1U)
            {
                return false;
            }
        }
        return true;
    }

    static bool endsWith(std::string const& value, std::string const& ending)
    {
        if (ending.size() > value.size())
        {
            return false;
        }
        return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
    }

    static shape getStrides(const shape& tensor_shape);

    static shape offsetToIndexes(size_t offset, const shape& strides);

    static size_t indexesToOffset(const shape& indexes, const shape& strides);
};

template<class T>
bool if_equals(const std::string&& error, const T val1, const T val2)
{
    if (val1 != val2)
    {
        throw(std::runtime_error(error));
    }
    return val1 == val2;
}

} // raul namespace

#endif // COMMON_H