#ifndef QDM_TAU_H
#define QDM_TAU_H 1

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_vector.h>

typedef struct {
  bool use_table;

  double low;
  double high;

  size_t spline_df;
  const gsl_vector *knots;

  gsl_vector *reset;
  gsl_matrix *ispline_table;
  gsl_matrix *mspline_table;
} qdm_tau;

qdm_tau *
qdm_tau_alloc(
    int use_table,

    double low,
    double high,

    size_t spline_df,
    const gsl_vector *knots
);

void
qdm_tau_free(
    qdm_tau *t
);

double
qdm_tau_ispline_mmm(
    const qdm_tau *t,
    const double value,
    const gsl_vector *mmm
);

double
qdm_tau_mspline_mmm(
    const qdm_tau *t,
    const double value,
    const gsl_vector *mmm
);

double
qdm_tau_find(
    const qdm_tau *t,
    const double v,
    const gsl_vector *mmm
);

#endif /* QDM_TAU_H */


/*
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aXe_grism.h"
#include "aXe_utils.h"
#include "inout_aper.h"
#include "aper_conf.h"
#include <gsl/gsl_multifit_nlin.h>
#include "trfit_utils.h"

#define AXE_IMAGE_PATH "AXE_IMAGE_PATH"
#define AXE_OUTPUT_PATH "AXE_OUTPUT_PATH"
#define AXE_CONFIG_PATH "AXE_CONFIG_PATH"

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

  char           *opt;

  char            grism_image[MAXCHAR];
  char            grism_image_path[MAXCHAR];

  char            conf_file[MAXCHAR];
  char            conf_file_path[MAXCHAR];

  char            aper_file[MAXCHAR];
  char            aper_file_path[MAXCHAR];

  int             i, j;
  int             num;

  object        **oblist;
  observation    *obs;

  aperture_conf  *conf;

  double exptime;

  gsl_vector *fit_result;

  if ((argc < 1) || (opt = get_online_option("help", argc, argv))) {
    fprintf(stdout,
	    "aXe_TFIT Version %s:\n"
	    "\n", RELEASE);
    exit(1);
  }
  fprintf(stdout, "aXe_TFIT: Starting...\n");

  // Get the data file name
  strcpy(grism_image, argv[1]);

  // Get the configuration file name
  strcpy(conf_file, argv[2]);
  build_path(AXE_CONFIG_PATH, conf_file, conf_file_path);

  // Read the configuration file
  conf = get_aperture_descriptor(conf_file_path);

  // Determine where the various extensions are in the FITS file
  build_path(AXE_IMAGE_PATH, grism_image, grism_image_path);
  get_extension_numbers(grism_image_path, conf, conf->optkey1, conf->optval1);

  // Get or set up the name of the output Aperture File
  if ((opt = get_online_option ("in_AF", argc, argv)))
    {
      /* get it */
      strcpy (aper_file, opt);
      strcpy (aper_file_path, opt);
    }
  else {
    // Build aperture file name
    replace_file_extension (grism_image, aper_file, ".fits",
			    ".OAF", conf->science_numext);
    build_path (AXE_OUTPUT_PATH, aper_file, aper_file_path);
  }

  fprintf(stdout,
	  "aXe_TFIT: Input configuration file name:   %s\n",
	  conf_file_path);
  fprintf(stdout,
	  "aXe_TFIT: Input data file name:            %s\n",
	  grism_image_path);
  fprintf(stdout,
	  "aXe_TFIT: SCI extension number:            %d\n",
	  conf->science_numext);
  fprintf(stdout,
	  "aXe_TFIT: ERR extension number:            %d\n",
	  conf->errors_numext);
  fprintf(stdout,
	  "aXe_TFIT: DQ extension number:             %d\n",
	  conf->dq_numext);
  fprintf(stdout,
	  "aXe_TFIT: DQ mask:                         %d\n",
	  conf->dqmask);
  fprintf(stdout,
	  "aXe_TFIT: Input aperture file name:        %s\n",
	  aper_file_path);
  fprintf(stdout, "\n\n");

  /* Loading the observation data */
  fprintf(stdout, "aXe_TFIT: ");

  //
  // try to get the descriptor 'exptime' from the 'sci'-extension
  //
  exptime = (double)get_float_from_keyword(grism_image_path,
					   conf->science_numext,
					   conf->exptimekey);
  if (isnan(exptime))
    exptime = (double)get_float_from_keyword(grism_image_path, 1,
					     conf->exptimekey);
  if (isnan(exptime))
    exptime = 1.0;

  // Load an image from AXE_DATA_PATH
  obs = load_image_t(grism_image_path, conf->science_numext,
		     conf->errors_numext, conf->dq_numext,
		     conf->dqmask, exptime, conf->rdnoise);

  // Loading the object list
  fprintf(stdout, "aXe_TFIT: Loading object list...");
  oblist = file_to_object_list_seq(aper_file_path, obs);
  fprintf(stdout, "%d objects loaded.\n", object_list_size(oblist));

  // initialize the counter
  i = 0;

  // check whether there are apertures
  if (oblist != NULL) {

    // go over all apertures
    while (oblist[i] != NULL) {

      // print the current aperture ID
      fprintf(stdout, "aXe_TFIT: Fitting object ID:%d", oblist[i]->ID);

      // go over all beams
      for (j = 0; j < oblist[i]->nbeams; j++) {

	// skip beam if ignore flag for thisbeam is set
	if (oblist[i]->beams[j].ignore !=0 || oblist[i]->beams[j].ID != 0)
	  {
	    /*fprintf(stdout,", %c ( Ignored )",
		    BEAM(oblist[i]->beams[j].ID));*/
	    continue;
	  }

	// print the current beam ID
	/*fprintf(stdout, ", %c (Fitted)", BEAM(oblist[i]->beams[j].ID));*/

	// fit the trace of the beam, modify the
	// beam definition with the new values
	// oblist[i]->beams[j].refpoint.y = fit_beamtrace(conf, obs, j, oblist[i]->beams[j]);
	fit_result = fit_beamtrace(conf, obs, j, oblist[i]->beams[j]);
	oblist[i]->beams[j].refpoint.x = gsl_vector_get(fit_result,0);
	oblist[i]->beams[j].refpoint.y = gsl_vector_get(fit_result,1);
	oblist[i]->beams[j].width = gsl_vector_get(fit_result,4);

	gsl_vector_free(fit_result);
      }

      // give feedback on the progress,
      // enhance the counter
      fprintf(stdout, " Done.\n");
      i++;
    }
  }

  // write the modified beam definitions
  // to an OAF file
  fprintf (stdout, "aXe_TFIT: Writing new aperture file...");fflush(stdout);
  num = object_list_to_file (oblist, aper_file_path, 1);
  fprintf (stdout, "%d beams written.\n", num);

  // release the memory
  free_observation(obs);
  if (oblist != NULL){
	free_oblist(oblist);}

  // print a last status message and exit
  fprintf(stdout, "aXe_TFIT: Done...\n");
  exit(0);
}


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <math.h>
#include <float.h>
#include <iniparser.h>
#include "tdsearch_gridsearch.h"
#ifdef TDSEARCH_USE_INTEL
#include <mkl_cblas.h>
#else
#include <cblas.h>
#endif
#include "tdsearch_greens.h"
#include "sacio.h"
#include "parmt_utils.h"
#include "iscl/array/array.h"
#include "iscl/memory/memory.h"
#include "iscl/os/os.h"
#include "iscl/signal/convolve.h"

#define NTSTAR 12   /*!< Default number of t*'s in the grid search. */
#define TSTAR0 0.1  /*!< Default first t* in grid search. */
#define TSTAR1 1.2  /*!< Default last t* in grid search. */
#define NDEPTH 12   /*!< Default number of depths in grid search. */
#define DEPTH0 0.0  /*!< First depth in grid search (km). */
#define DEPTH1 3.0  /*!< Last depth in grid search (km). */
#define DOUBLE_PAGE_SIZE sysconf(_SC_PAGE_SIZE)/sizeof(double)
#define PAGE_SIZE sysconf(_SC_PAGE_SIZE)
#define LDG 6

/*!
 * @brief Sets the default grid search parameters.
 *
 * @param[out] tds   On exit contains the default grid search settings such
 *                   as the t* range, depth range, and toggles off the max
 *                   time shift.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_setDefaults(struct tdSearch_struct *tds)
{
    int ierr;
    memset(tds, 0, sizeof(struct tdSearch_struct));
    tds->maxShiftTime = DBL_MAX;
    ierr = tdsearch_gridSearch_defineTstarGrid(NTSTAR, TSTAR0, TSTAR1, tds);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Failed to set t* grid\n", __func__);
        return -1;
    }
    ierr = tdsearch_gridSearch_defineDepthGrid(NDEPTH, DEPTH0, DEPTH1, tds);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Failed to set depth grid\n", __func__);
        return -1;
    }
    return ierr;
}
//============================================================================//
/*!
 * @brief Frees memory on the grid-search structure.
 *
 * @param[out] tds    On exit all memory has been freed and parameters set to
 *                    0 or NULL.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_free(struct tdSearch_struct *tds)
{
    memory_free64f(&tds->G);
    memory_free64f(&tds->Gxc);
    memory_free64f(&tds->tstar);
    memory_free64f(&tds->depths);
    memory_free64f(&tds->synthetic);
    memory_free64f(&tds->xcorr);
    memory_free32i(&tds->lags);
    memory_free32i(&tds->grnsMatrixPtr);
    memory_free32i(&tds->grnsXCMatrixPtr);
    memset(tds, 0, sizeof(struct tdSearch_struct));
    return 0;
}
//============================================================================//
/*!
 * @brief Defines the t* grid in grid search.
 *
 * @param[in] ntstars    Number of t*'s in grid search (> 0).
 * @param[in] tstar0     First t* in grid search (>= 0).
 * @param[in] tstar1     Final t* in grid search (>= tstar0).
 *
 * @param[out] tds       Grid search structure with t* grid.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_defineTstarGrid(const int ntstars, const double tstar0,
                                        const double tstar1,
                                        struct tdSearch_struct *tds)
{
    int ierr;
    tds->ntstar = 0;
    if (ntstars < 1 || tstar0 < 0.0 || tstar0 > tstar1)
    {
        if (ntstars < 1)
        {
            fprintf(stderr, "%s: Error no t*'s %d in search\n", __func__, ntstars);
        }
        if (tstar0 < 0.0)
        {
            fprintf(stderr, "%s: Error tstar0 %f must be non-negative\n",
                    __func__, tstar0);
        }
        if (tstar0 > tstar1)
        {
            fprintf(stderr, "%s: Error tstar0 %f > tstar1 %f\n",
                    __func__, tstar0, tstar1);
        }
        return -1;
    }
    tds->ntstar = ntstars;
    memory_free64f(&tds->tstar);
    tds->tstar = array_linspace64f(tstar0, tstar1, ntstars, &ierr);
    return ierr;
}
//============================================================================//
/*!
 * @brief Defines the depth grid in grid search.
 *
 * @param[in] ndepths    Number of depths in grid search (> 0).
 * @param[in] depth0     First depth (km) in grid search (>= 0).
 * @param[in] depth1     Final depth (km) in grid search (>= depth0).
 *
 * @param[out] tds       Grid search structure with depth grid.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_defineDepthGrid(const int ndepths, const double depth0,
                                        const double depth1,
                                        struct tdSearch_struct *tds)
{
    int ierr;
    tds->ndepth = 0;
    if (ndepths < 1 || depth0 < 0.0 || depth0 > depth1)
    {
        if (ndepths < 1)
        {
            fprintf(stderr, "%s: Error no depthss %d in search\n",
                    __func__, ndepths);
        }
        if (depth0 < 0.0)
        {
            fprintf(stderr, "%s: Error depth0 %f must be non-negative\n",
                    __func__, depth0);
        }
        if (depth0 > depth1)
        {
            fprintf(stderr, "%s: Error depth0 %f > depth1 %f\n",
                    __func__, depth0, depth1);
        }
        return -1;
    }
    tds->ndepth = ndepths;
    memory_free64f(&tds->depths);
    tds->depths = array_linspace64f(depth0, depth1, ndepths, &ierr);
    return ierr;
}
//============================================================================//
/*!
 * @brief Reads the grid search parameters from the ini file.
 *
 * @param[in] iniFile    Name of ini file with parameters.
 *
 * @param[out] tds       On successful exit contains the grid search parameters.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_initializeFromFile(const char *iniFile,
                                           struct tdSearch_struct *tds)
{
    double depth0, depth1, tstar0, tstar1;
    dictionary *ini;
    int ierr, ndepth, ntstar;
    // Set the defaults
    ierr = 0;
    tdsearch_gridSearch_setDefaults(tds);
    // Verify the file exists
    if (!os_path_isfile(iniFile))
    {
        fprintf(stderr, "%s: Error ini file %s doesn't exist\n", __func__, iniFile);
        return -1;
    }
    ini = iniparser_load(iniFile);
    // Unpack the parameter file
    tds->maxShiftTime
      = iniparser_getdouble(ini, "tdSearch:gridSearch:maxShiftTime\0", DBL_MAX);
    if (tds->maxShiftTime < DBL_MAX)
    {
        if (tds->maxShiftTime <= 0.0)
        {
            fprintf(stderr, "%s: Max shift time must be positive\n", __func__);
            ierr = 1;
            goto ERROR;
        }
    }
    // Number of t*'s
    ntstar = iniparser_getint(ini, "tdSearch:gridSearch:ntstar\0", NTSTAR);
    if (ntstar < 1)
    {
        fprintf(stderr, "%s: number of t*'s must be positive\n", __func__);
        ierr = 1;
        goto ERROR;
    }
    // Number of depths
    ndepth = iniparser_getint(ini, "tdSearch:gridSearch:ndepths\0", NDEPTH);
    if (ndepth < 1)
    {
        fprintf(stderr, "%s: number of depths must be positive\n", __func__);
        ierr = 1;
        goto ERROR;
    }
    // Depth grid search range
    depth0 = iniparser_getdouble(ini, "tdSearch:gridSearch:depth0\0", DEPTH0);
    depth1 = iniparser_getdouble(ini, "tdSearch:gridSearch:depth1\0", DEPTH1);
    if (depth0 >= depth1)
    {
        fprintf(stderr, "%s: depth0 >= depth1 is invalid\n", __func__);
        ierr = 1;
        goto ERROR;
    }
    // t* grid search range
    tstar0 = iniparser_getdouble(ini, "tdSearch:gridSearch:tstar0\0", TSTAR0);
    tstar1 = iniparser_getdouble(ini, "tdSearch:gridSearch:tstar1\0", TSTAR1);
    if (tstar0 >= tstar1)
    {
        fprintf(stderr, "%s: tstar0 >= tstar1 is invalid\n", __func__);
        ierr = 1;
        goto ERROR;
    }
    // Create the grid
    ierr = tdsearch_gridSearch_defineTstarGrid(ntstar,
                                               tstar0, tstar1,
                                               tds);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Error defining t* grid\n", __func__);
        goto ERROR;
    }
    ierr = tdsearch_gridSearch_defineDepthGrid(ndepth,
                                               depth0, depth1,
                                               tds);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Error defining depth grid\n", __func__);
        goto ERROR;
    }
ERROR:;
    iniparser_freedict(ini);
    return ierr;
}
//============================================================================//
/*!
 * @brief Returns the number of depths in the grid search.
 *
 * @param[in] tds     Holds the number of t*'s.
 *
 * @result The number of t*'s in the grid search.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_getNumberOfTstars(const struct tdSearch_struct tds)
{
    return tds.ntstar;
}
//============================================================================//
/*!
 * @brief Returns the number of depths in the grid search.
 *
 * @param[in] tds     Holds the number of depths.
 *
 * @result The number of depths in the grid search.
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_getNumberOfDepths(const struct tdSearch_struct tds)
{
    return tds.ndepth;
}
//============================================================================//
/*!
 * @brief Convenience utility for computing the index corresponding to the
 *        2D row major depth x t* grid.
 *
 * @param[in] it      The t* index [0,ntstar-1].
 * @param[in] id      The depth indx [0,ndepth-1].
 * @param[in] ntstar  The number of t*'s in the grid search.
 *
 * @result The grid search index (= id*ntstar + it).
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_gridToIndex2(const int it, const int id,
                                     const int ntstar)
{
    int idt;
    idt = id*ntstar + it;
    return idt;
}
//============================================================================//
/*!
 * @brief Convenience utility for computing the index corresponding to the
 *        2D row major depth x t* grid.
 *
 * @param[in] it      The t* index [0,ntstar-1].
 * @param[in] id      The depth indx [0,ndepth-1].
 * @param[in] tds     Grid search structure holding the number of ntstar's
 *                    in the grid search.
 *
 * @result The grid search index (= id*tds.ntstar + it).
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_gridToIndex(const int it, const int id,
                                    const struct tdSearch_struct tds)
{
    int idt, ntstar;
    ntstar = tdsearch_gridSearch_getNumberOfTstars(tds);
    idt = tdsearch_gridSearch_gridToIndex2(it, id, ntstar);
    return idt;
}
//============================================================================//
/*!
 * @brief Computes the forward modeling matrix, G, so that estimates, e, can
 *        can be efficiently generated via \f$ G m = e \f$ for any moment
 *        tensor, m.  The columns of G represent cross-correlations of the
 *        Green's functions corresponding to the \f$ m_{ij} \f$ moment tensor
 *        component so that the estimate, e, is a cross-correlogram.
 *
 * @param[in] iobs     Observation index in the range [0,nobs-1].
 * @param[in] data     Structure containing the data.
 * @param[in] grns     Structure containing the Green's functions.
 *
 * @param[in,out] tds  On input contains the initialized tds structure.
 * @param[in,out] tds  On exit the forward modeling matrix G has been set.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdsearch_gridSearch_setForwardModelingMatrices(
    const int iobs,
    const struct tdSearchData_struct data,
    const struct tdSearchGreens_struct grns,
    struct tdSearch_struct *tds)
{
    double *G, *Gxc, *work;
    int *grnsMatrixPtr, *grnsXCMatrixPtr, indices[6],
        i, idep, idt, ierr, indx, it, jndx, kndx,
        lxc, lxcMax, lxc0, ntstar, ndepth, npgrns, npts;
    // Reset pointers
    memory_free64f(&tds->G);
    memory_free64f(&tds->Gxc);
    memory_free32i(&tds->grnsMatrixPtr);
    memory_free32i(&tds->grnsXCMatrixPtr);
    // Check sizes
    ndepth = grns.ndepth;
    ntstar = grns.ntstar;
    if (ndepth != tds->ndepth || ntstar != tds->ntstar)
    {
        if (ndepth != tds->ndepth)
        {
            fprintf(stderr, "%s: Error ndepth %d != tds->ndepth %d\n",
                    __func__, ndepth, tds->ndepth);
        }
        if (ntstar != tds->ntstar)
        {
            fprintf(stderr, "%s: Error ntstar %d != tds->ntstar %d\n",
                    __func__, ntstar, tds->ntstar);
        }
        return -1;
    }
    npts = data.obs[iobs].npts;
    if (npts < 1)
    {
        fprintf(stderr, "%s: Error data has no points\n", __func__);
        return -1;
    }
    tds->dnorm = cblas_dnrm2(npts, data.obs[iobs].data, 1);
    if (fabs(tds->dnorm) < DBL_EPSILON)
    {
        fprintf(stderr, "%s: Error - data is identically zero\n", __func__);
        return -1;
    }
    tds->npts = npts;
    sacio_getFloatHeader(SAC_FLOAT_DELTA, data.obs[iobs].header, &tds->dt);
    // Set the Green's functions forward modeling matrix size
    lxcMax = 0;
    grnsMatrixPtr = memory_calloc32i(ndepth*ntstar+1);
    grnsXCMatrixPtr = memory_calloc32i(ndepth*ntstar+1);
    grnsMatrixPtr[0] = 0;
    grnsXCMatrixPtr[0] = 0;
    for (idep=0; idep<ndepth; idep++)
    {
        for (it=0; it<ntstar; it++)
        {
            // Get the Green's functions indices
            ierr = tdsearch_greens_getGreensFunctionsIndices(iobs, it,
                                                             idep, grns,
                                                             indices);
            if (ierr != 0)
            {
                fprintf(stderr, "%s: Failed to get indices\n", __func__);
                return -1;
            }
            // Compute the cross-correlation length at (dep, t*)
            idt = tdsearch_gridSearch_gridToIndex2(it, idep, ntstar);
            lxc0 = 0;
            for (i=0; i<6; i++)
            {
                indx = indices[i];
                npgrns = grns.grns[indx].npts;
                if (npgrns < 1)
                {
                    fprintf(stderr, "%s: Error - no points in grns fn\n", __func__);
                    return -1;
                }
                lxc = npgrns + npts - 1;
                lxcMax = MAX(lxcMax, lxc);
                if (i == 0)
                {
                    lxc0 = lxc;
                    grnsMatrixPtr[idt+1] = grnsMatrixPtr[idt] + npgrns;
                    grnsXCMatrixPtr[idt+1] = grnsXCMatrixPtr[idt] + lxc;
                }
                if (lxc != lxc0)
                {
                    fprintf(stderr, "%s: Inconsistent column sizes\n", __func__);
                    return -1;
                }
            }
        }
    }
    if (lxcMax < 1)
    {
        fprintf(stderr, "%s: Error - cross-correlations are empty\n", __func__);
        return -1;
    }
    // Set the forward modeling matrices
    tds->nptsGrns = grnsMatrixPtr[ndepth*ntstar];
    G = memory_calloc64f(LDG*grnsMatrixPtr[ndepth*ntstar]);
    Gxc = memory_calloc64f(LDG*grnsXCMatrixPtr[ndepth*ntstar]);
    work = memory_calloc64f(lxcMax);
    for (idep=0; idep<ndepth; idep++)
    {
        for (it=0; it<ntstar; it++)
        {
            idt = tdsearch_gridSearch_gridToIndex2(it, idep, ntstar);
            // Get the Green's functions indices
            ierr = tdsearch_greens_getGreensFunctionsIndices(iobs, it,
                                                             idep, grns,
                                                             indices);
            // Compute the cross-correlation length at (dep, t*)
            idt = tdsearch_gridSearch_gridToIndex2(it, idep, ntstar);
            jndx = LDG*grnsMatrixPtr[idt];
            kndx = LDG*grnsXCMatrixPtr[idt];
            for (i=0; i<6; i++)
            {
                indx = indices[i];
                npgrns = grns.grns[indx].npts;
                signal_convolve_correlate64f_work(npts, data.obs[iobs].data,
                                                  npgrns, grns.grns[indx].data,
                                                  CONVCOR_FULL,
                                                  lxc, work);
                //printf("%e %e\n",cblas_dnrm2(npgrns, grns.grns[indx].data, 1),
                //               cblas_dnrm2(lxc, work, 1));
                cblas_dcopy(npgrns, grns.grns[indx].data, 1, &G[jndx+i],   LDG);
                cblas_dcopy(lxc,    work,                 1, &Gxc[kndx+i], LDG);
            }
            //printf("%d\n", grnsMatrixPtr[idt+1]);
        }
    }
    tds->G = G;
    tds->Gxc = Gxc;
    tds->grnsMatrixPtr = grnsMatrixPtr;
    tds->grnsXCMatrixPtr = grnsXCMatrixPtr;
    memory_free64f(&work);
    return 0;
}
//============================================================================//
/*!
 * @brief Sets the moment tensor on the tdSearch structure.
 *
 * @param[in] m6    moment tensor in NED system packed:
 *                   \f$ \{ m_{xx}, m_{yy}, m_{zz},
 *                         m_{xy}, m_{xz}, m_{yz} \} \f$.
 *
 * @param[out] tds  On successful exit contains the moment tensor.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 * @author Ben Baker, ISTI
 *
 */
int tdSearch_gridSearch_setMomentTensor(const double *__restrict__ m6,
                                        struct tdSearch_struct *tds)
{
    tds->lhaveMT = false;
    array_zeros64f_work(8, tds->mt); // NOTE - this is padded
    if (m6 == NULL)
    {
        fprintf(stderr, "%s: Error moment tensor cannot be NULL\n", __func__);
        return -1;
    }
    array_copy64f_work(6, m6, tds->mt);
    tds->lhaveMT = true;
    return 0;
}
//============================================================================//
/*!
 * @brief Sets the moment tensor elements on the grid search structure.
 *
 * @param[in] m11      m11 moment tensor in Newton meters.
 * @param[in] m22      m22 moment tensor in Newton meters.
 * @param[in] m33      m33 moment tensor in Newton meters.
 * @param[in] m12      m12 moment tensor in Newton meters.
 * @param[in] m13      m13 moment tensor in Newton meters.
 * @param[in] m23      m23 moment tensor in Newton meters.
 * @param[in] basis    The appropriate moment tensor basis.
 * @param[in] basis    CE_USE indicates the moment tensor is in up, south
 *                     east coordinates like what one would obtain from
 *                     the GCMT catalog.
 * @param[in] basis    CE_NED indicates the moment tensor is in north, east,
 *                     down coordinates and is consistent with Programs in
 *                     Seismology.
 *
 * @param[in,out] tds  On input contains the initialized grid search
 *                     structure.
 * @param[in,out] tds  On exit contains the input moment tensor.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 */
int tdSearch_gridSearch_setMomentTensorFromElements(
    const double m11, const double m22, const double m33,
    const double m12, const double m13, const double m23,
    const enum compearthCoordSystem_enum basis,
    struct tdSearch_struct *tds)
{
    double mt[6], mtNED[6];
    int ierr;
    tds->lhaveMT = false;
    mt[0] = m11;
    mt[1] = m22;
    mt[2] = m33;
    mt[3] = m12;
    mt[4] = m13;
    mt[5] = m23;
    ierr = compearth_convertMT(1, basis, CE_NED, mt, mtNED);
    if (ierr != 0)
    {
        printf("%s: Failed to set moment tensor\n", __func__);
        return -1;
    }
    ierr = tdSearch_gridSearch_setMomentTensor(mtNED, tds);
    if (ierr != 0)
    {
        printf("%s: Failed to set moment tensor\n", __func__);
        return -1;
    }
    return 0;
}
//============================================================================//
/*!
 * @brief Sets the moment tensor information from an event structure.
 *
 * @param[in] event    Contains the event on which the moment tensor is defined.
 * @param[in,out] tds  On input this is the initialized grid search structure.
 * @param[in,out] tds  On exit contains the given moment tensor.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 */
int tdSearch_gridSearch_setMomentTensorFromEvent(
    const struct tdSearchEventParms_struct event,
    struct tdSearch_struct *tds)
{
    int ierr;
    ierr = tdSearch_gridSearch_setMomentTensorFromElements(
                    event.m11, event.m22, event.m33,
                    event.m12, event.m13, event.m23,
                    event.basis, tds);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Error setting moment tensor\n", __func__);
        return -1;
    }
    return 0;
}
//============================================================================//
/*!
 * @brief Computes the cross-correlations between the data and synthetics
 *        at each (t*,depth) in the grid search.
 *
 * @param[in,out] tds   On input contains the initialized grid search
 *                      structure with the Green's functions and source
 *                      properties.
 * @param[in,out] tds   On exit contains the result of the cross-correlations
 *                      and lags at each (t*,depth) in the grid search.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 */
int tdSearch_gridSearch_performGridSearch(struct tdSearch_struct *tds)
{
    double *u, *ud, dnorm, unorm;
    int id, idt, indx, iopt, it, jndx, l1, l2, lxc, maxlags,
        ndepth, ndt, nlag, npgrns, nrows, ntstar;
    const int ncols = 6;
    enum isclError_enum ierr;
    tds->itopt =-1;
    tds->idopt =-1;
    ndepth = tds->ndepth;
    ntstar = tds->ntstar;
    ndt = ntstar*ndepth;
    dnorm = tds->dnorm;
    memory_free64f(&tds->xcorr);
    memory_free64f(&tds->synthetic);
    memory_free32i(&tds->lags);
    // Some easy checks
    if (!tds->lhaveMT)
    {
        fprintf(stderr, "%s: Error moment tensor not set\n", __func__);
        return -1;
    }
    if (tds->G == NULL || tds->Gxc == NULL ||
        tds->grnsMatrixPtr == NULL || tds->grnsXCMatrixPtr == NULL)
    {
        fprintf(stderr, "%s: You need to set the forward modeling matrices\n", __func__);
        return -1;
    }
    maxlags =-1;
    if (tds->maxShiftTime < DBL_MAX)
    {
        maxlags = (int) (tds->maxShiftTime/tds->dt + 0.5);
    }
    // Set output space
    tds->xcorr = memory_calloc64f(ndt);
    tds->lags = memory_calloc32i(ndt);
    // Compute the cross-correlation
    nrows = tds->grnsXCMatrixPtr[ndt];
    ud = memory_calloc64f(nrows);
    cblas_dgemv(CblasRowMajor, CblasNoTrans,
                nrows, ncols, 1.0, tds->Gxc, LDG,
                tds->mt, 1, 0.0, ud, 1);
    // Compute the synthetics
    nrows = tds->grnsMatrixPtr[ndt];
    u = memory_calloc64f(nrows);
    cblas_dgemv(CblasRowMajor, CblasNoTrans,
                nrows, ncols, 1.0, tds->G, LDG,
                tds->mt, 1, 0.0, u, 1);
    // Now do the grid search
    for (id=0; id<ndepth; id++)
    {
        for (it=0; it<ntstar; it++)
        {
            idt = tdsearch_gridSearch_gridToIndex(it, id, *tds);
            indx = tds->grnsMatrixPtr[idt];
            jndx = tds->grnsXCMatrixPtr[idt];
            lxc = tds->grnsXCMatrixPtr[idt+1]
                - tds->grnsXCMatrixPtr[idt];
            npgrns = tds->grnsMatrixPtr[idt+1]
                   - tds->grnsMatrixPtr[idt];
            unorm = cblas_dnrm2(npgrns, &u[indx], 1);
            if (maxlags < 0)
            {
                iopt = array_argmax64f(lxc, &ud[jndx], &ierr);
            }
            else
            {
                l1 = MAX(0,  npgrns - maxlags);
                l2 = MIN(lxc-1, npgrns + maxlags);
                nlag = l2 - l1 + 1;
                iopt = l1 + array_argmax64f(nlag, &ud[jndx+l1], &ierr);
 /*
for (int k=0; k<lxc; k++)
{
printf("%d %e\n",k, ud[jndx+k]);
}
getchar();
*/
            }
            tds->xcorr[idt] = ud[jndx+iopt]/(dnorm*unorm);
            // Compute the lag where the times range from [-npts+1:npgrns-1]
            tds->lags[idt] =-npgrns + 1 + iopt;
            //printf("%f %f %f %d\n", tds->depths[id], tds->tstar[it], xc, idelay);
        }
        //printf("\n");
    }
    tds->synthetic = u;
    memory_free64f(&ud);
    // Compute the optimum indices
    iopt = array_argmax64f(ndt, tds->xcorr, &ierr);
    tds->idopt = iopt/tds->ntstar;
    tds->itopt = iopt - tds->idopt*tds->ntstar;
    if (tdsearch_gridSearch_gridToIndex(tds->itopt, tds->idopt, *tds) != iopt)
    {
        fprintf(stdout, "%s: Switching to slow optimum computation\n", __func__);
        for (id=0; id<ndepth; id++)
        {
            for (it=0; it<ntstar; it++)
            {
                idt = tdsearch_gridSearch_gridToIndex(it, id, *tds);
                if (iopt == idt)
                {
                    tds->idopt = id;
                    tds->itopt = it;
                }
            }
        }
    }
    return 0;
}
//============================================================================//
/*!
 * @brief Writes a map of the max of the cross-correlograms as a function
 *        of t* and depth in a format useful for Gnpulot as well as a
 *        corresponding Gnuplot plotting script.
 *
 * @param[in] dirnm   Directory where the heat map is to be written.
 * @param[in] froot   The root name of the heat map.
 * @param[in] tds     Grid search structure with the tabulated
 *                    cross-correlogram maximums.
 *
 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 *
 */
int tdsearch_gridSearch_writeHeatMap(const char *dirnm, const char *froot,
                                     const struct tdSearch_struct tds)
{
    FILE *ofl;
    char fname[PATH_MAX], fgnu[PATH_MAX], cmd[256];
    int id, idt, it;
    memset(fname, 0, PATH_MAX*sizeof(char));
    if (!tds.xcorr || !tds.lags)
    {
        fprintf(stderr, "%s: Error gridsearch likely not performed\n", __func__);
        return -1;
    }
    if (!tds.tstar || !tds.depths)
    {
        fprintf(stderr, "%s: Error tds likely not initialized\n", __func__);
        return -1;
    }
    if (dirnm != NULL)
    {
        sprintf(fname, "%s/%s.surf_txt", dirnm, froot);
    }
    else
    {
        sprintf(fname, "./%s.surf_txt", froot);
    }
    ofl = fopen(fname, "w");
    for (id=0; id<tds.ndepth; id++)
    {
        for (it=0; it<tds.ntstar; it++)
        {
            idt = tdsearch_gridSearch_gridToIndex(it, id, tds);
            fprintf(ofl, "%f %f %e %d\n",
                    tds.tstar[it], tds.depths[id],
                    tds.xcorr[idt], tds.lags[idt]);
        }
       fprintf(ofl, "\n");
    }
    fclose(ofl);
    // and the gnuplot file
    memset(fgnu, 0, PATH_MAX*sizeof(char));
    if (dirnm != NULL)
    {
        sprintf(fgnu, "%s/%s.gnu", dirnm, froot);
    }
    else
    {
        sprintf(fgnu, "./%s.gnu", froot);
    }
    ofl = fopen(fgnu, "w");
    fprintf(ofl, "#!/usr/bin/gnuplot -persist\n");
    fprintf(ofl, "set pm3d map\n");
    fprintf(ofl, "set xlabel 't*'\n");
    fprintf(ofl, "set ylabel 'Depth (km)'\n");
    fprintf(ofl, "set title 'Cross-Correlation'\n");
    fprintf(ofl, "set grid\n");
    fprintf(ofl, "set xrange [%f:%f]\n", tds.tstar[0],
                                         tds.tstar[MAX(0, tds.ntstar-1)]);
    fprintf(ofl, "set yrange [%f:%f]\n", tds.depths[MAX(0, tds.ndepth-1)],
                                         tds.depths[0]);
    fprintf(ofl, "splot '%s.surf_txt' u 1:2:3\n", froot);
    fclose(ofl);
    // change permissions
    memset(cmd, 0, 256*sizeof(char));
    sprintf(cmd, "chmod 0755 %s", fgnu);
    system(cmd);
    return 0;
}
//============================================================================//
/*!
 * @brief Makes a shifted synthetic seismogram corresponding to the
 *        given observation, t*, and depth.
 *
 * @param[in] iobs    Observation index in the range [0,nobs-1].
 * @param[in] it      t* index in the range [0,tds.ntstar-1].
 * @param[in] id      Depth index in the range [0,tds.ndepth].
 * @param[in] data    Contains the observed seismograms.
 * @param[in] grns    Contains the Green's functions used to make
 *                    the synthetic seisogram.
 * @param[in] tds     Contains the time lags computed in the grid search.
 *
 * @param[out] synth  Structure containing the synthetic seismogram.

 * @result 0 indicates success.
 *
 * @ingroup tdsearch_gridsearch
 */
int tdSearch_gridSearch_makeSACSynthetic(
    const int iobs, const int it, const int id,
    const struct tdSearchData_struct data,
    const struct tdSearchGreens_struct grns,
    const struct tdSearch_struct tds,
    struct sacData_struct *synth)
{
    double epoch;
    int idt, ierr, igrns, npGrns;
    memset(synth, 0, sizeof(struct sacData_struct));
    // Pick off the header
    if (it < 0 || it >= tds.ntstar || id < 0 || id >= tds.ndepth)
    {
        fprintf(stderr, "%s: Invalid (t*,depth) index (%d,%d)\n", __func__, it, id);
        return - 1;
    }
    if (tds.synthetic == NULL || tds.lags == NULL)
    {
        if (tds.synthetic == NULL)
        {
            fprintf(stderr, "%s: Error tds.synethetic is NULL\n", __func__);
        }
        if (tds.lags == NULL)
        {
            fprintf(stderr, "%s: Error tds.lags is NULL\n", __func__);
        }
        return -1;
    }
    idt = tdsearch_gridSearch_gridToIndex(it, id, tds);
    igrns = tdsearch_greens_getGreensFunctionIndex(G11_GRNS, iobs,
                                                   it, id, grns);
    if (idt < 0 || igrns < 0)
    {
        fprintf(stderr, "%s: Failed getting an index\n", __func__);
        return -1;
    }
    // Copy header
    sacio_copyHeader(grns.grns[igrns].header, &synth->header);
    // Copy corresponding synthetic
    synth->npts = grns.grns[igrns].npts;
    synth->data = sacio_malloc64f(synth->npts);
    npGrns = tds.grnsMatrixPtr[idt+1] - tds.grnsMatrixPtr[idt];
    if (npGrns != synth->npts)
    {
        fprintf(stderr, "%s: Size mismatch\n", __func__);
        sacio_free(synth);
        return -1;
    }
    array_copy64f_work(npGrns, &tds.synthetic[tds.grnsMatrixPtr[idt]],
                       synth->data);
    // Get the component inline with the estimate
    sacio_setCharacterHeader(SAC_CHAR_KCMPNM, data.obs[iobs].header.kcmpnm,
                             &synth->header);
    // Fix the timing
    ierr = sacio_getEpochalStartTime(synth->header, &epoch);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Failed to get start time\n", __func__);
        return -1;
    }
    epoch = epoch + (double) tds.lags[idt]*tds.dt;
    sacio_setEpochalStartTime(epoch, &synth->header);
    return 0;
}
//============================================================================//


#ifndef TRACKER_GMD_H
#define TRACKER_GMD_H

#include "tracker.h"

#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h> /* GAUSSIAN*/
#include "helper/Constants.h"
#include <limits.h>
#include "helper/high_res_timer.h"
#include "helper/bounding_box_regressor.h"

class TrackerGMD : public Tracker {

public:
  TrackerGMD(const bool show_tracking, ExampleGenerator* example_generator,  RegressorTrainBase* regressor_train);

  // Estimate the location of the target object in the current image.
  virtual void Track(const cv::Mat& image_curr, RegressorBase* regressor,
             BoundingBox* bbox_estimate_uncentered);

  // After tracking for this frame, update internal state
  virtual void UpdateState(const cv::Mat& image_curr, BoundingBox &bbox_estimate, RegressorBase* regressor, bool is_last_frame);

  // Initialize the tracker with the ground-truth bounding box of the first frame.
  virtual void Init(const cv::Mat& image_curr, const BoundingBox& bbox_gt,
            RegressorBase* regressor);

  virtual void Init(const std::string& image_curr_path, const VOTRegion& region,
            RegressorBase* regressor);

  // Online fine tune, given the networks and example_generators
  virtual void FineTuneOnline(ExampleGenerator* example_generator,
                                RegressorTrainBase* regressor_train, bool success_frame, bool is_last_frame);

  // Actual worker to do the finetune
  void FineTuneWorker(ExampleGenerator* example_generator,
                                RegressorTrainBase* regressor_train,
                                std::vector<int> &this_bag,
                                const int pos_candidate_upper_bound = INT_MAX,
                                const int neg_candidate_upper_bound = INT_MAX);

  // Motion Model around bbox_curr_prior_tight_
  void GetCandidates(BoundingBox &cur_bbox, int W, int H, std::vector<BoundingBox> &candidate_bboxes);

  // Check if generated candidate is valid or not
  bool ValidCandidate(BoundingBox &candidate_bbox, int W, int H);

  // Get one moved box, according to the Gaussian Motion Model
  BoundingBox GenerateOneGaussianCandidate(int W, int H, BoundingBox &bbox, double sd_x = SD_X, double sd_y = SD_Y,
                                           double sd_scale = SD_SCALE, double sd_ap = SD_AP);

  // Create and Enqueue Training Samples given already set up example_generator
  virtual void EnqueueOnlineTraningSamples(ExampleGenerator* example_generator, const cv::Mat &image_curr, const BoundingBox &estimate,  bool success_frame);

  // check if the current estimate is success, needed as flag to pass to EnqueueOnlineTraningSamples
  virtual bool IsSuccessEstimate();

  // clear all the related storage for tracking net video
  virtual void Reset(RegressorBase *regressor);

private:
  gsl_rng *rng_;

  // Used to generate additional training examples through synthetic transformations.
  ExampleGenerator* example_generator_;

  // Neural network.
  RegressorTrainBase* regressor_train_;

  // this prediction scores for candidates
  std::vector<float> candidate_probabilities_;
  std::vector<BoundingBox> candidates_bboxes_;
  std::vector<int> sorted_idxes_; // the sorted indexes of probabilities from high to low

  // samples collected along each frame
  std::vector<BoundingBox> gts_;
  std::vector<cv::Mat> image_currs_;
  std::vector<cv::Mat> images_finetune_;
  std::vector<cv::Mat> targets_finetune_;
  std::vector<std::vector<BoundingBox> > candidates_finetune_pos_;
  std::vector<std::vector<BoundingBox> > candidates_finetune_neg_;
  // std::vector<std::vector<double> > labels_finetune_pos_;
  // std::vector<std::vector<double> > labels_finetune_neg_;

  // long term and short term
  std::vector<int> short_term_bag_;
  std::vector<int> long_term_bag_;

  std::mt19937 engine_;

  // for motion model candidates
  double sd_trans_;
  double sd_scale_;
  double sd_ap_;

  // timer
  HighResTimer hrt_;

  // Bbox regressor
  BoundingBoxRegressor bbox_finetuner_;

};

#endif


#pragma once

// v1.2 By Ook
//

#include <concepts>
#include <gsl/gsl>

namespace EnumConcepts
{
	template <class T>
	concept IsEnum = std::is_enum_v<T>;

	template<class _Ty>
	struct is_Numeric
		: std::integral_constant<bool,
		(std::is_arithmetic_v<_Ty> || std::is_same_v<_Ty, std::byte> || std::is_enum_v<_Ty>)
		&& !std::is_same_v<_Ty, wchar_t>
		&& !std::is_same_v<_Ty, char>
		&& !std::is_pointer_v<_Ty>
		//&& !std::is_enum_v<_Ty>
		>
	{
	};

	// determine whether T is a Number type (excluding char / wchar), same as is_Numeric<T>::value
	template <typename T>
	constexpr bool is_Numeric_v = is_Numeric<T>::value;

	template <class T>
	concept IsNumeric = is_Numeric_v<T>;
}

//template <EnumConcepts::IsEnum E>
//constexpr E& operator |=(E& eStyle, const E& dStyle) noexcept
//{
//	if constexpr (sizeof(E) <= sizeof(size_t))
//		eStyle = static_cast<E>(static_cast<size_t>(eStyle) | static_cast<size_t>(dStyle));
//	else
//		eStyle = static_cast<E>(static_cast<uint64_t>(eStyle) | static_cast<uint64_t>(dStyle));
//	return eStyle;
//}

template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
constexpr E& operator |=(E& eStyle, const T& dStyle) noexcept
{
	if constexpr(sizeof(E) <= sizeof(size_t) && sizeof(T) <= sizeof(size_t))
		eStyle = static_cast<E>(static_cast<size_t>(eStyle) | static_cast<size_t>(dStyle));
	else
		eStyle = static_cast<E>(static_cast<uint64_t>(eStyle) | static_cast<uint64_t>(dStyle));
	return eStyle;
}

//template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
//constexpr T& operator |=(T& dStyle, const E& eStyle) noexcept
//{
//	//if constexpr (sizeof(E) <= sizeof(size_t) && sizeof(T) <= sizeof(size_t))
//	//	dStyle = static_cast<T>(static_cast<size_t>(eStyle) | static_cast<size_t>(dStyle));
//	//else
//	//	dStyle = static_cast<T>(static_cast<uint64_t>(eStyle) | static_cast<uint64_t>(dStyle));
//	//return dStyle;
//
//	dStyle |= static_cast<T>(eStyle);
//
//	return dStyle;
//}

template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
constexpr E operator |(const E& eStyle, const T& dStyle) noexcept
{
	if constexpr (sizeof(E) <= sizeof(size_t) && sizeof(T) <= sizeof(size_t))
		return static_cast<E>(static_cast<size_t>(eStyle) | static_cast<size_t>(dStyle));
	else
		return static_cast<E>(static_cast<uint64_t>(eStyle) | static_cast<uint64_t>(dStyle));
}

template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
constexpr E& operator &=(E& eStyle, const T& dStyle) noexcept
{
	if constexpr (sizeof(E) <= sizeof(size_t) && sizeof(T) <= sizeof(size_t))
		eStyle = static_cast<E>(static_cast<size_t>(eStyle) & static_cast<size_t>(dStyle));
	else
		eStyle = static_cast<E>(static_cast<uint64_t>(eStyle) & static_cast<uint64_t>(dStyle));
	return eStyle;
}

template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
constexpr E operator &(const E& eStyle, const T& dStyle) noexcept
{
	if constexpr (sizeof(E) <= sizeof(size_t) && sizeof(T) <= sizeof(size_t))
		return static_cast<E>(static_cast<size_t>(eStyle) & static_cast<size_t>(dStyle));
	else
		return static_cast<E>(static_cast<uint64_t>(eStyle) & static_cast<uint64_t>(dStyle));
}

template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
constexpr E operator ^(const E& eStyle, const T& dStyle) noexcept
{
	if constexpr (sizeof(E) <= sizeof(size_t) && sizeof(T) <= sizeof(size_t))
		return static_cast<E>(static_cast<size_t>(eStyle) ^ static_cast<size_t>(dStyle));
	else
		return static_cast<E>(static_cast<uint64_t>(eStyle) ^ static_cast<uint64_t>(dStyle));
}

template <EnumConcepts::IsEnum E>
constexpr E operator ~(const E& eStyle) noexcept
{
	if constexpr (sizeof(E) <= sizeof(size_t))
		return static_cast<E>(~static_cast<size_t>(eStyle));
	else
		return static_cast<E>(~static_cast<uint64_t>(eStyle));
}

template <EnumConcepts::IsNumeric T, EnumConcepts::IsEnum E>
constexpr E to_Enum(T t) noexcept
{
	return static_cast<E>(t);
}


#include <stdio.h>
#include <math.h>
#include <ascii.h>
#include <cdisort.h>
#include <c_tzs.h>
#include "locate.h"
#if HAVE_LIBGSL
#include <gsl/gsl_sf_expint.h>
#endif

// include DISORT.MXD

/*  nlyr   = number of atmospheric layers                     */
/*  nlev_c = number of atmospheric levels on common z grid    */
/*  zd_c   = atmospheric levels on common z grid              */
/*  nzout  = number of output levels = number of black clouds */
/*  zout   = vertical level heights in km above surface       */

int c_tzs (int      nlyr,
           float*   dtauc,
           int      nlev_c,
           float*   zd_c,
           int      nzout,
           float*   zout,
           float*   ssalb,
           float*   temper,
           float    wvnmlo,
           float    wvnmhi,
           int      usrtau,
           int      ntau,
           float*   utau,
           int      usrang,
           int      numu,
           float*   umu,
           int      nphi,
           float*   phi,
           float    albedo,
           double   btemp,
           float    ttemp,
           float    temis,
           int      planck,
           int*     prnt,
           char*    header,  //  int prndis[7]; char header[127]
           float*   rfldir,
           float*   rfldn,
           float*   flup,
           float*   dfdt,
           float*   uavg,
           float*** uu,
           int      quiet) {
  /*    .. Internal variables  */
  int     lc, j, iu, lu;
  double* tauc = NULL;
  double  tau1 = 0, tau2 = 0, etau1 = 0, etau2 = 0, etaumu = 0, ei1 = 0, ei2 = 0, *rad = NULL;
  double *radem = NULL, *pkag = NULL, *etau = NULL, rademtot = 0.0;
  double  radrefl, radrefl1, radrefl2, radrefl11, radrefl22;
  double *b0 = NULL, *b1 = NULL;
  double *coeff1 = NULL, *coeff2 = NULL;
  double  sqtau1 = 0.0, sqtau2 = 0.0, mu = 0.0, cutau1 = 0.0, tauetau1;
  int *   nlayers = NULL, maxnlyr = 0;
  float * alb = NULL, *bplanck = NULL;

  /* nlyr     = number of layers */
  /* nlyr + 1 = number of levels */

  /* initialise */

  if ((rad = (double*)calloc (ntau, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((nlayers = (int*)calloc (ntau, sizeof (int))) == NULL)
    return ASCII_NO_MEMORY;

  if ((alb = (float*)calloc (ntau, sizeof (float))) == NULL)
    return ASCII_NO_MEMORY;
  if ((bplanck = (float*)calloc (ntau, sizeof (float))) == NULL)
    return ASCII_NO_MEMORY;

  if (prnt[0] && strlen (header) != 0) {
    fprintf (stderr, "**************\n TZS: %s\n*****************\n", header);
  }

  /* Initialise everything to 0. */
  for (iu = 0; iu < numu; iu++) {
    for (lu = 0; lu < nzout; lu++) {
      for (j = 0; j < nphi; j++) {
        uu[j][lu][iu] = 0.;
      }
    }
  }
  for (lu = 0; lu < nzout; lu++) {
    dfdt[lu]   = 0.;
    flup[lu]   = 0.;
    rfldir[lu] = 0.;
    rfldn[lu]  = 0.;
    uavg[lu]   = 0.;
  }

  /*    Layers: 0...NLYR-1 (# layers = NLYR)                                                           */
  /*    Levels: 0...NLYR   (# levels = NLYR+1)                                                         */
  /*    LC = 0...NYR-1                                                                                */
  /*    LC = 0 : TOA                                                                                */
  /*    -------------------------------------------------- Level LC, TAUC(LC), TEMPER(LC)              */
  /*                                                                                */
  /*                  Layer LC                                                                         */
  /*                                                                                */
  /*    -------------------------------------------------- Level LC+1  , TAUC(LC+1)  , TEMPER(LC+1)    */

  /*    Planck radiation from surface with emissivity=1-ALBEDO and temperature BTEMP */
  /*     BPLANCK = (1-ALBEDO)*PLKAVG2( WVNMLO, WVNMHI, BTEMP )  */

  /*    Max number of layers counted from TOA used for all UTAU  */
  maxnlyr = -1;

  /*    Loop over user defined tau = black cloud top heights:  */
  /*    determine layer number corresponding to user tau       */
  /*    Rem.: NTAU = NZOUT checked in CHEKIN_TZS               */

  if (ntau != nzout) {
    errmsg_tzs ("TZS--nzout != ntau", TZS_ERROR);
  }

  for (lu = 0; lu < ntau; lu++) {

    /* zd_c[0] = TOA                                                         */
    /* zd_c[nlyr+1] = SUR                                                    */
    /* locate gives back lc such that zd_c(lc) >= zout(lu) > zd_lc[lc+1]     */
    lc = flocate (zd_c, nlev_c, zout[lu]);

    //    fprintf(stderr,"lc = %d  zd_c[lc] = %f  zd_c[lc+1] = %f  zout[lu] = %f\n",lc,zd_c[lc],zd_c[lc+1],zout[lu]);

    if (lc == -1 || lc == nlev_c) {
      errmsg_tzs ("TZS--Zout outside atmospheric levels", TZS_ERROR);
    }

    /* Check real presence of this level in tauc ( max diff = 1mm )                         */
    /* Determine layer above given user level and store layer index w.r.t. zd_c in nlayers  */
    /* nlayers contains the layer index whose bottom is at zout[lu]                         */

    if (fabs (zd_c[lc + 1] - zout[lu]) <= 1.e-6) {

      /*          check lower boundary           */

      /*          nlevel = nlayers[lu] = LC+1-1  */
      /*          nlayer = nlevel - 1            */
      nlayers[lu] = lc;

    } else if (fabs (zd_c[lc] - zout[lu]) <= 1.e-6) {

      /*          check upper boundary           */

      /*          nlevel = nlayers[lu] = LC      */
      /*          nlayer = nlevel - 1            */
      nlayers[lu] = (int)fmax (lc - 1, 0);
      /* nlayers could be -1 when black cloud is at TOA */

    } else {

      /* zout_interpolate */

      //      fprintf(stderr,"%d4 %4d %9.3f %9.3f %9.3f %9.3f %9.3f\n",
      //	      lc,nlev_c,zd_c[lc+1],zd_c[lc],zout[lu],fabs(zd_c[lc+1]-zout[lu]),fabs(zd_c[lc]-zout[lu]));
      errmsg_tzs ("TZS--User height is not contained in atmospheric levels: use zout_interpolate!", TZS_ERROR);
    }

    //    fprintf(stderr,"nlev_c = %4d   zout[lu] = %9.3f  zd_c[nlayers[lu]] = %9.3f   zd_c[nlayers[lu]+1] = %9.3f   lu = %4d\n",
    //	    nlev_c,zout[lu],zd_c[nlayers[lu]],zd_c[nlayers[lu]+1],lu);
    //
    //    fprintf(stderr,"lu = %4d   maxnlyr = %4d   nlayers[lu] = %4d\n",lu,maxnlyr,nlayers[lu]);

    maxnlyr = (int)fmax (maxnlyr, nlayers[lu]);
  }

  /* Above we defined layer indices, now we add +1 because we want the number of layers */
  maxnlyr += 1;

  //  fprintf(stderr,"%s %4d\n","Max # layers = ",maxnlyr);

  if (maxnlyr < 0) {
    errmsg_tzs ("TZS--wrong zout", TZS_ERROR);
  }

  if ((radem = (double*)calloc (maxnlyr, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((tauc = (double*)calloc (maxnlyr + 1, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((pkag = (double*)calloc (maxnlyr + 1, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((etau = (double*)calloc (maxnlyr + 1, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;

  if ((b0 = (double*)calloc (maxnlyr, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((b1 = (double*)calloc (maxnlyr, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((coeff1 = (double*)calloc (maxnlyr, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;
  if ((coeff2 = (double*)calloc (maxnlyr, sizeof (double))) == NULL)
    return ASCII_NO_MEMORY;

  /*    calculate cumulative optical depth tauc[lc] up to level lc and      */
  /*    Planck radiation for every level (not layer!) from 0 to MAXNLYR     */
  /*    Remark: layer 1 is the first layer from above=TOA??                   */
  /*            level 0 is the first level from above=TOA, level 0 = TOA    */

  /* lc = level count */
  lc       = 0;
  tauc[lc] = 0.0;
  pkag[lc] = (double)c_planck_func1 (wvnmlo, wvnmhi, temper[lc]);

  //  fprintf(stderr,"lc = %d  maxnlyr = %d  pkag[lc] = %f  tauc[lc] = %13.6e dtauc[lc-1] = %13.6e temper[lc] = %9.3f\n",lc,maxnlyr,pkag[lc],tauc[lc],dtauc[lc-1],temper[lc]);

  for (lc = 1; lc <= maxnlyr; lc++) {
    /* We work here with atmospheric optical thickness as it is provided by the user. */
    /* If you want to use aborption optical thickness the you have to define it in    */
    /* the uvspec input file.                                                         */
    tauc[lc] = tauc[lc - 1] + ((double)dtauc[lc - 1]);

    pkag[lc] = (double)c_planck_func1 (wvnmlo, wvnmhi, temper[lc]);

    //    fprintf(stderr,"lc = %d  maxnlyr = %d  pkag[lc] = %f  tauc[lc] = %13.6e dtauc[lc-1] = %13.6e temper[lc] = %9.3f\n",lc,maxnlyr,pkag[lc],tauc[lc],dtauc[lc-1],temper[lc]);
  }

  /*    Print input information  */
  if (prnt[0]) {

    prtinp_tzs (nlyr,
                maxnlyr,
                dtauc,
                ssalb,
                temper,
                wvnmlo,
                wvnmhi,
                ntau,
                utau,
                numu,
                umu,
                nphi,
                phi,
                albedo,
                btemp,
                ttemp,
                temis,
                tauc,
                nlev_c,
                zd_c,
                nzout,
                zout);
  }

  /*    check input dimensions and variables */
  chekin_tzs (nlyr,
              maxnlyr,
              dtauc,
              nlev_c,
              zd_c,
              nzout,
              zout,
              ssalb,
              temper,
              wvnmlo,
              wvnmhi,
              usrtau,
              ntau,
              utau,
              usrang,
              numu,
              umu,
              nphi,
              phi,
              albedo,
              btemp,
              ttemp,
              temis,
              planck,
              tauc,
              quiet);

  for (lu = 0; lu < ntau; lu++) {
    //    fprintf(stderr,"lu = %4d  nlayers[lu] = %4d   nlyr = %4d\n",lu,nlayers[lu],nlyr);
    /* check layer bottom level */
    if (nlayers[lu] + 1 == nlyr) {
      /*          real surface albedo = 1 - emissivity                                         */
      alb[lu] = albedo;
      /*          Planck radiation from surface with emissivity=1-ALBEDO and temperature BTEMP */
      bplanck[lu] = (1 - alb[lu]) * c_planck_func1 (wvnmlo, wvnmhi, btemp);
      //      fprintf(stderr,"%4d  %4d  %12.10f  %12.9f  %12.8f\n",lu, nlayers[lu]+1,alb[lu],bplanck[lu],btemp);
    } else {
      /*          Real surface albedo = 1 - emissivity ==> emissivity=1 (black cloud)          */
      alb[lu] = 0.0;
      /*          Planck radiation of bottom level with emissivity=1 (black cloud)             */
      /*          and temperature temper( nlayers[lu]+1 ) of bottom level                      */
      bplanck[lu] = (float)pkag[nlayers[lu] + 1];
    }
  }

  if (prnt[0]) {
    fprintf (stderr, "\n%4d %s", ntau, " User optical depths :");
    for (lu = 0; lu < ntau; lu++)
      fprintf (stderr, "%14.8f", utau[lu]);
    fprintf (stderr, "\n");

    fprintf (stderr, "\n%4d %s", ntau, " User level numbers :");
    for (lu = 0; lu < ntau; lu++)
      fprintf (stderr, "%4d", nlayers[lu] + 1);
    fprintf (stderr, "\n");

    fprintf (stderr, "\n%4d %s", ntau, " User levels / km :");
    for (lu = 0; lu < ntau; lu++)
      fprintf (stderr, "%14.8f", zout[lu]);
    fprintf (stderr, "\n");

    fprintf (stderr, "\n%4d %s", ntau, " User levels / km :");
    for (lu = 0; lu < ntau; lu++)
      fprintf (stderr, "%14.8f", zd_c[nlayers[lu] + 1]);
    fprintf (stderr, "\n");
  }

  /* loop from TOA to lowest layer/level needed for calculations */
  for (lc = 0; lc < maxnlyr; lc++) {
    /*          assume linear variation of Planck radiation from one level                     */
    /*          to the next one: Planck=b0+tauc*b1                                             */
    b1[lc] = 0.;

    /* CE: this threshold was commented out, but it seems that it is needed because otherwise we get
     negative emissions in some layers */
    if (dtauc[lc] > 1.e-4)
      b1[lc] = (pkag[lc + 1] - pkag[lc]) / dtauc[lc];
    /* In case of thin atmosphere but high temperature variation from one level to the next, dtauc is very small  */
    /* but Planck varies sensibly, so we have to take care. The threshold 1.e10 is not crucial because thin       */
    /* atmospheric layers emit few radiation.                                                                     */
    if (fabs (b1[lc]) > 1.e10) {
      /* set b0 to average Planck emission, also not crucial */
      b0[lc] = 0.5 * (pkag[lc + 1] + pkag[lc]);
      b1[lc] = 0.0;
    } else {
      b0[lc] = pkag[lc] - tauc[lc] * b1[lc];
    }

    /* Compute coefficients like this even if coeff1[lc]=pkag[lc+1] and coeff2[lc]=pkag[lc]    */
    coeff1[lc] = b0[lc] + tauc[lc + 1] * b1[lc];
    coeff2[lc] = b0[lc] + tauc[lc] * b1[lc];

    //    fprintf(stderr," LC %d Deltatau %e DIFFCOEFF1 %e COEFF1 %e pkag[lc+1] = %e\n",lc,dtauc[lc],coeff1[lc]-pkag[lc+1],coeff1[lc],pkag[lc+1]);
    //    fprintf(stderr," LC %d Deltatau %e DIFFCOEFF2 %e COEFF2 %e pkag[lc]   = %e\n",lc,dtauc[lc],coeff2[lc]-pkag[lc],coeff2[lc],pkag[lc]);
  }

  /*    Loop over UMUs    */
  for (iu = 0; iu < numu; iu++) {

    /*       Double precision UMU  */
    mu = (double)umu[iu];

    //    /* loop from TOA to lowest layer/level needed for calculations */
    //    for (lc=0;lc<=maxnlyr;lc++){
    //      /*          Initialise ETAU = Attenuation factors from TOA to levels LC in direction MU  */
    //      etau[lc]=exp(-tauc[lc]/mu);
    //    }

    /*       Emission of atmosphere:   */
    /*       ***********************   */

    /*          Initialise ETAU = Attenuation factors from TOA to level LC in direction MU   */
    lc       = 0;
    etau[lc] = exp (-tauc[lc] / mu);

    /* loop from TOA to lowest layer/level needed for calculations */

    for (lc = 0; lc < maxnlyr; lc++) {

      /*          Initialise ETAU = Attenuation factors from TOA to level LC+1 in direction MU */
      etau[lc + 1] = exp (-tauc[lc + 1] / mu);

      /*          Attenuation factors from TOA to levels LC+1 resp. LC:   */
      /*           ETAU[lc+1]                                             */
      /*           ETAU[lc]                                               */

      /*          Radiation emitted from layer LC (between levels LC and LC+1) towards TOA   */
      radem[lc] = etau[lc] * coeff2[lc] - etau[lc + 1] * coeff1[lc] + b1[lc] * mu * (etau[lc] - etau[lc + 1]);
    }

    /*       Loop over user defined tau = black cloud top heights   */
    for (lu = 0; lu < ntau; lu++) {

      //      fprintf(stderr,"lu = %4d  %4d\n",lu,nlayers[lu]);

      /*          Initialise RAD to 0.              */
      rad[lu] = 0.;
      /*          Initialise TAU1                   */
      /*          to optical thickness from TOA up to bottom of layer nlayers[lu] */
      /*           TAU1  = TAUC(NLYR)               */
      tau1 = tauc[nlayers[lu] + 1];
      /*          Initialise ETAU1                  */
      etau1    = exp (-tau1);
      tauetau1 = tau1 * etau1;
      /*          Initialise EI1                    */

#if HAVE_LIBGSL
      /* exponential integral does not converge for values larger about 600*/
      /* the integral for this case becomes 0*/
      if (tau1 > 500.0)
        ei1 = 0.0;
      else
        ei1 = gsl_sf_expint_Ei (-tau1);
        /* fprintf(stderr, "lu %d  tau1 %g ei1 %g\n",lu,  tau1, ei1);  */
#endif
#if !HAVE_LIBGSL
      fprintf (stderr, "libRadtran was built without the gsl-library. Thus \n");
      fprintf (stderr, "tzs may not be used. Please install gsl on your system\n");
      fprintf (stderr, "and rebuild libRadtran.\n");
      exit (0);
#endif

      /*          Initialise SQTAU1                 */
      sqtau1 = tau1 * tau1;
      /*          Initialise CUTAU1                 */
      cutau1 = tau1 * sqtau1;
      /*          initialise RADREFL11              */
      radrefl11 = -2.0 * etau1 + tauetau1 - tau1 * tauetau1 - cutau1 * ei1;

      /*          Attenuation factor due to full atmospheric absorption in direction MU  */
      etaumu = etau[nlayers[lu] + 1];

      /* total atmospheric emission i.e. intergral from TOA to bottom */
      rademtot = 0.0;

      /*          Contribution from surface + atmosphere              */
      for (lc = 0; lc <= nlayers[lu]; lc++) {

        if (alb[lu] > 0.0) {

          /*             Reflection from surface:   */
          /*             ************************   */
          tau2      = tau1;
          etau2     = etau1;
          ei2       = ei1;
          sqtau2    = sqtau1;
          radrefl22 = radrefl11;
          radrefl2  = etau2 * coeff2[lc] * (1. - tau2) - coeff2[lc] * sqtau2 * ei2;

          if (lc == nlayers[lu]) {
            radrefl1  = coeff1[lc];
            radrefl11 = -2.0;
          } else {
            tau1     = tauc[nlayers[lu] + 1] - tauc[lc + 1];
            etau1    = exp (-tau1);
            tauetau1 = tau1 * etau1;

#if HAVE_LIBGSL
            /* exponential integral does not converge for values larger about 600*/
            /* the integral for this case becomes 0*/
            if (tau1 > 500.0)
              ei1 = 0.0;
            else
              ei1 = gsl_sf_expint_Ei (-tau1);
#endif

            sqtau1 = tau1 * tau1;
            cutau1 = tau1 * sqtau1;

            radrefl11 = -2.0 * etau1 + tauetau1 - tau1 * tauetau1 - cutau1 * ei1;

            radrefl1 = etau1 * coeff1[lc] * (1. - tau1) - coeff1[lc] * sqtau1 * ei1;
          }

          radrefl = radrefl1 - radrefl2;

          /* fprintf(stderr,"%s %4d %4d %13.6e\n","LC NLAYERS RADREFL[lc]",lc,nlayers[lu],radrefl);  */
          /* fprintf(stderr,"%s %4d %13.6e\n","LC RADREFL1",lc,radrefl1);                          */
          /* fprintf(stderr,"%s %4d %13.6e\n","LC RADREFL2",lc,radrefl2);                          */
          /* fprintf(stderr,"%s %4d %13.6e %13.6e\n","LC COEFF1 COEFF2",lc,coeff1[lc],coeff2[lc]); */
          /* fprintf(stderr,"%s %4d %13.6e %13.6e %13.6e %13.6e\n","LC TAU1 TAU2 SQTAU1 SQTAU2",lc,tau1,tau2,sqtau1,sqtau2); */
          /* fprintf(stderr,"%s %4d %13.6e %13.6e\n","LC EI1 EI2",lc,ei1,ei2); */

          radrefl += b1[lc] / 3. * (radrefl11 - radrefl22);

          //	  fprintf(stderr,"%s %4d %13.6e\n","LC RADREFL[lc]",lc,radrefl);
          //	  fprintf(stderr,"%s %4d %13.6e\n","LC B1[lc]",lc,b1[lc]);
          //	  fprintf(stderr,"%s %4d %13.6e\n","LC RADREFL11",lc,radrefl11);
          //	  fprintf(stderr,"%s %4d %13.6e\n","LC RADREFL22",lc,radrefl22);

          /*              RAD[lu]   = RAD[lu]+RADEM[lc]+ALBEDO*ETAUMU*RADREFL      */
          rad[lu] += radrefl;
        }

        /* emission of atmosphere */
        rademtot += radem[lc];
      }

      /* fprintf(stderr,"lu = %4d   rad[lu] refl = %13.6e\n",lu,rad[lu]); */

      /* after computation of radiance reflected at the surface we multiply by the albedo and the extinction from bottom to TOA */
      /*              RAD[lu]   = RAD[lu]+RADEM[lc]+ALBEDO*ETAUMU*RADREFL      */
      rad[lu] *= alb[lu] * etaumu;

      /* fprintf(stderr,"lu = %4d   rad[lu] refl attenuated = %13.6e\n",lu,rad[lu]); */

      /*          Contribution from atmospheric emission of all levels           */
      rad[lu] += rademtot;

      /* fprintf(stderr,"lu = %4d   rad[lu] refl attenuated + emission = %13.6e\n",lu,rad[lu]); */

      /*          contribution from surface emission (bottom):     */
      /*          ********************************************     */
      rad[lu] += bplanck[lu] * etaumu;

      /* fprintf(stderr,"lu = %4d   rad[lu] refl attenuated + emission + surface = %13.6e\n",lu,rad[lu]); */

      /* isotropic radiance                               */
      for (j = 0; j < nphi; j++) {
        uu[j][lu][iu] = (float)rad[lu];
      }
      uavg[lu] = (float)rad[lu];
      /*       End loop over user tau          */
    }

    /*    End loop over user umu    */
  }

  free (radem);
  free (tauc);
  free (pkag);
  free (etau);
  free (b0);
  free (b1);
  free (coeff1);
  free (coeff2);

  return 0;
}

/*============================= errmsg_tzs() ===============================*/

/*
 * Print out a warning or error message;  abort if type == TZS_ERROR
 */

#define MAX_WARNINGS 100

void errmsg_tzs (char* messag, int type) {
  static int warning_limit = FALSE, num_warnings = 0;

  if (type == TZS_ERROR) {
    fprintf (stderr, "\n ******* ERROR >>>>>>  %s\n", messag);
    exit (1);
  }

  if (warning_limit)
    return;

  if (++num_warnings <= MAX_WARNINGS) {
    fprintf (stderr, "\n ******* WARNING >>>>>>  %s\n", messag);
  } else {
    fprintf (stderr, "\n\n >>>>>>  TOO MANY WARNING MESSAGES --  They will no longer be printed  <<<<<<<\n\n");
    warning_limit = TRUE;
  }

  return;
}

#undef MAX_WARNINGS

void prtinp_tzs (int     nlyr,
                 int     maxnlyr,
                 float*  dtauc,
                 float*  ssalb,
                 float*  temper,
                 float   wvnmlo,
                 float   wvnmhi,
                 int     ntau,
                 float*  utau,
                 int     numu,
                 float*  umu,
                 int     nphi,
                 float*  phi,
                 float   albedo,
                 double  btemp,
                 float   ttemp,
                 float   temis,
                 double* tauc,
                 int     nlev_c,
                 float*  zd_c,
                 int     nzout,
                 float*  zout) {
  /*     print values of input variables                                 */
  /*                                                                     */
  /*   Called by- TZS                                                    */
  /* --------------------------------------------------------------------*/

  /*     .. Local Scalars ..  */
  int lc, lu;

  fprintf (stderr, "\n%s%4d\n", " No. computational layers        = ", nlyr);
  fprintf (stderr, "\n%s%4d\n", " No. computational layers needed = ", maxnlyr);

  fprintf (stderr, "\n%4d %s", ntau, " User optical depths :");
  for (lu = 0; lu < ntau; lu++)
    fprintf (stderr, "%13.6e", utau[lu]);
  fprintf (stderr, "\n");

  fprintf (stderr, "\n%4d %s", numu, " User polar angle cosines :");
  for (lu = 0; lu < numu; lu++)
    fprintf (stderr, "%9.5f", umu[lu]);
  fprintf (stderr, "\n");

  fprintf (stderr, "\n%4d %s", nphi, " User azimuthal angles :");
  for (lu = 0; lu < nphi; lu++)
    fprintf (stderr, "%9.2f", phi[lu]);
  fprintf (stderr, "\n");

  fprintf (stderr,
           "%s%14.4f%14.4f\n%s%10.2f%s%8.4f\n%s%8.4f\n",
           "    Thermal emission in wavenumber interval :",
           wvnmlo,
           wvnmhi,
           "    Bottom temperature = ",
           btemp,
           "    Top temperature = ",
           ttemp,
           "    Top emissivity = ",
           temis);

  fprintf (stderr, "%s%8.4f\n", "    Bottom albedo (Lambertian) =", albedo);

  /* Print layer variables */

  fprintf (stderr,
           "\n%s\n%s\n%s\n",
           "            Layer        Total        Single",
           "          Optical      Optical    Scattering",
           "            Depth        Depth        Albedo   Temperature");

  for (lc = 0; lc < maxnlyr; lc++) {
    fprintf (stderr, "%4d%13.6e%13.6e%14.3f%14.3f\n", lc, dtauc[lc], tauc[lc], ssalb[lc], temper[lc]);
  }

  fprintf (stderr, "%4d%13.6e%13.6e%14.3f%14.3f\n", maxnlyr, 0.0, tauc[maxnlyr], 0.0, temper[maxnlyr]);

  fprintf (stderr,
           "\n%s\n%s\n%s\n",
           "            Layer        Single",
           "          Optical    Scattering",
           "            Depth        Albedo   Temperature");

  for (lc = 0; lc < nlyr; lc++) {
    fprintf (stderr, "%4d%13.6e%14.3f%14.3f\n", lc, dtauc[lc], ssalb[lc], temper[lc]);
  }

  fprintf (stderr, "%4d%13.6e%14.3f%14.3f\n", nlyr, -1.0, -1.0, temper[nlyr]);

  fprintf (stderr, "\n%s\n%s\n%s\n", "      Atmospheric", "            Level", "             [km]");

  for (lc = 0; lc < nlev_c; lc++) {
    fprintf (stderr, "%4d     %8.3f\n", lc, zd_c[lc]);
  }

  fprintf (stderr, "\n%s\n%s\n%s\n", "           Output", "            Level", "             [km]");

  for (lc = 0; lc < nzout; lc++) {
    fprintf (stderr, "%4d     %8.3f\n", lc, zout[lc]);
  }
}

void chekin_tzs (int     nlyr,
                 int     maxnlyr,
                 float*  dtauc,
                 int     nlev_c,
                 float*  zd_c,
                 int     nzout,
                 float*  zout,
                 float*  ssalb,
                 float*  temper,
                 float   wvnmlo,
                 float   wvnmhi,
                 int     usrtau,
                 int     ntau,
                 float*  utau,
                 int     usrang,
                 int     numu,
                 float*  umu,
                 int     nphi,
                 float*  phi,
                 float   albedo,
                 double  btemp,
                 float   ttemp,
                 float   temis,
                 int     planck,
                 double* tauc,
                 int     quiet) {
  /*     Checks the input dimensions and variables  */
  int inperr = FALSE;
  int lc, j, iu, lu;

  if (planck == FALSE)
    inperr = c_write_bad_var (VERBOSE, "planck");

  if (nlyr < 1)
    inperr = c_write_bad_var (VERBOSE, "NLYR");

  if (maxnlyr < 1)
    inperr = c_write_bad_var (VERBOSE, "MAXNLYR");

  if (nlyr + 1 != nlev_c)
    inperr = c_write_bad_var (VERBOSE, "NLYR/NLEV_C");

  for (lc = 0; lc < nlyr; lc++) {
    if (dtauc[lc] < 0.) {
      inperr = c_write_bad_var (VERBOSE, "dtauc");
    }
    if (zd_c[lc] < 0.) {
      inperr = c_write_bad_var (VERBOSE, "zd");
    }
    if (ssalb[lc] < 0.0 || ssalb[lc] > 1.0) {
      inperr = c_write_bad_var (VERBOSE, "ssalb");
    }
    if (temper[lc] < 0.) {
      inperr = c_write_bad_var (VERBOSE, "temper");
    }
  }

  if (usrtau) {
    if (ntau < 1) {
      inperr = c_write_bad_var (VERBOSE, "ntau");
    }
    if (nzout < ntau)
      inperr = c_write_bad_var (VERBOSE, "ntau/nzout");
    for (lu = 0; lu < ntau; lu++) {
      /* Do a relative check to see if we are just beyond the bottom boundary */
      /* This might happen due to numerical rounding off problems.  ak20110224*/

      //      fprintf(stderr,"lu = %d  utau = %13.6e  tauc[maxnlyr] = %13.6e  zout = %f\n",lu,utau[lu],tauc[maxnlyr],zout[lu]);
      if (fabs (utau[lu] - tauc[maxnlyr]) <= 1.e-6 * tauc[maxnlyr]) {
        utau[lu] = tauc[maxnlyr];
      }
      //      fprintf(stderr,"lu = %d  utau = %13.6e  tauc[maxnlyr] = %13.6e  zout = %f\n",lu,utau[lu],tauc[maxnlyr],zout[lu]);

      //      if(utau[lu] < 0. || utau[lu] > tauc[maxnlyr]) {
      if (utau[lu] < 0. || (utau[lu] - tauc[maxnlyr]) > 1.e-6 * tauc[maxnlyr]) {
        fprintf (stderr,
                 "WARNING: lu = %d  utau = %13.6e  tauc[maxnlyr] = %13.6e  zout = %f\n",
                 lu,
                 utau[lu],
                 tauc[maxnlyr],
                 zout[lu]);
        inperr = c_write_bad_var (VERBOSE, "utau");
      }
      if (zout[lu] < 0.0)
        inperr = c_write_bad_var (VERBOSE, "zout");
    }
  } else {
    ntau    = 1;
    utau    = (float*)calloc (1, sizeof (float));
    utau[0] = 0.;
    nzout   = 1;
    zout    = (float*)calloc (1, sizeof (float));
    zout[0] = zd_c[0];
  }

  if (usrang) {
    if (numu < 0) {
      inperr = c_write_bad_var (VERBOSE, "numu");
    }
    for (iu = 0; iu < numu; iu++) {
      if (umu[iu] < -1. || umu[iu] > 1. || umu[iu] == 0.) {
        inperr = c_write_bad_var (VERBOSE, "umu");
      }
      if (iu >= 1) {
        if (umu[iu] < umu[iu - 1]) {
          inperr = c_write_bad_var (VERBOSE, "umu");
        }
      }
    }
    if (nphi <= 0)
      inperr = c_write_bad_var (VERBOSE, "nphi");

    for (j = 0; j < nphi; j++) {
      if (phi[j] < 0. || phi[j] > 360.) {
        inperr = c_write_bad_var (VERBOSE, "phi");
      }
    }
  } else {
    numu   = 1;
    umu    = (float*)calloc (1, sizeof (float));
    umu[0] = 1.;
    nphi   = 1;
    phi    = (float*)calloc (1, sizeof (float));
    phi[0] = 0.;
  }

  if (albedo < 0. || albedo > 1.)
    inperr = c_write_bad_var (VERBOSE, "albedo");

  if (wvnmlo < 0.0 || wvnmhi <= wvnmlo)
    inperr = c_write_bad_var (VERBOSE, "wvnmlo,hi");

  if (temis < 0. || temis > 1.)
    inperr = c_write_bad_var (VERBOSE, "temis");

  if (btemp < 0.0)
    inperr = c_write_bad_var (VERBOSE, "btemp");

  if (ttemp < 0.0)
    inperr = c_write_bad_var (VERBOSE, "ttemp");

  if (inperr)
    c_errmsg ("TZS--input and/or dimension errors", TZS_ERROR);

  if (!quiet) {
    for (lc = 1; lc <= nlyr; lc++) {
      if (fabs (temper[lc] - temper[lc - 1]) > 10.) {
        c_errmsg ("check_inputs--vertical temperature step may be too large for good accuracy", TZS_WARNING);
      }
    }
  }
}


/*
 * Copyright (c) 1997-1999 Massachusetts Institute of Technology
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ifndef FFTW_MPI_H
#define FFTW_MPI_H

#include <fftw.h>
#include <mpi.h> /* need access to the MPI type definitions */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/***********************************************************************/

typedef fftw_real TRANSPOSE_EL_TYPE;

typedef struct {
     int block_num, dest_pe, send_size, recv_size;
} transpose_mpi_exchange;

typedef struct {
     MPI_Comm comm;
     int n_pes, my_pe;

     int nx,ny,local_nx,local_ny;

     transpose_mpi_exchange *exchange;
     int num_steps, send_block_size, recv_block_size;

     MPI_Datatype el_type;

     MPI_Request request[2];

     int *perm_block_dest;
     int num_perm_blocks, perm_block_size;

     int all_blocks_equal;
     int *send_block_sizes, *send_block_offsets;
     int *recv_block_sizes, *recv_block_offsets;

     char *move;
     int move_size;
} transpose_mpi_plan_struct;

typedef transpose_mpi_plan_struct *transpose_mpi_plan;

extern void transpose_mpi_get_local_size(int n, int my_pe, int n_pes,
					 int *local_n, int *local_start);
extern int transpose_mpi_get_local_storage_size(int nx, int ny,
						int my_pe, int n_pes);

extern transpose_mpi_plan transpose_mpi_create_plan(int nx, int ny,
						    MPI_Comm comm);
extern void transpose_mpi_destroy_plan(transpose_mpi_plan p);

extern void transpose_mpi(transpose_mpi_plan p, int el_size,
			  TRANSPOSE_EL_TYPE *local_data,
			  TRANSPOSE_EL_TYPE *work);

typedef enum { BEFORE_TRANSPOSE, AFTER_TRANSPOSE } transpose_in_place_which;

typedef enum { TRANSPOSE_SYNC, TRANSPOSE_ASYNC } transpose_sync_type;

extern void transpose_in_place_local(transpose_mpi_plan p,
                              int el_size, TRANSPOSE_EL_TYPE *local_data,
                              transpose_in_place_which which);

extern TRANSPOSE_EL_TYPE *transpose_allocate_send_buf(transpose_mpi_plan p,
						      int el_size);
extern void transpose_get_send_block(transpose_mpi_plan p, int step,
				     int *block_y_start, int *block_ny);
extern void transpose_start_exchange_step(transpose_mpi_plan p,
					  int el_size,
					  TRANSPOSE_EL_TYPE *local_data,
					  TRANSPOSE_EL_TYPE *send_buf,
					  int step,
					  transpose_sync_type sync_type);
extern void transpose_finish_exchange_step(transpose_mpi_plan p, int step);

/***********************************************************************/

typedef struct {
     fftw_plan p_fft_x;  /* plan for first dimension */
     fftwnd_plan p_fft;  /* plan for subsequent dimensions */
     transpose_mpi_plan p_transpose, p_transpose_inv;
     fftw_complex *work; /* extra workspace, if needed */
} fftwnd_mpi_plan_data;

typedef fftwnd_mpi_plan_data *fftwnd_mpi_plan;

typedef enum {
    FFTW_NORMAL_ORDER,
    FFTW_TRANSPOSED_ORDER
} fftwnd_mpi_output_order;

extern fftwnd_mpi_plan fftwnd_mpi_create_plan(MPI_Comm comm,
					      int rank, const int *n,
					      fftw_direction dir,
					      int flags);
extern fftwnd_mpi_plan fftw2d_mpi_create_plan(MPI_Comm comm,
					      int nx, int ny,
					  fftw_direction dir, int flags);
extern fftwnd_mpi_plan fftw3d_mpi_create_plan(MPI_Comm comm,
					      int nx, int ny, int nz,
					  fftw_direction dir, int flags);

extern void fftwnd_mpi_destroy_plan(fftwnd_mpi_plan p);

extern void fftwnd_mpi_local_sizes(fftwnd_mpi_plan p,
				   int *local_nx,
				   int *local_x_start,
				   int *local_ny_after_transpose,
				   int *local_y_start_after_transpose,
				   int *total_local_size);

extern void fftwnd_mpi(fftwnd_mpi_plan p,
		       int n_fields,
		       fftw_complex *local_data, fftw_complex *work,
		       fftwnd_mpi_output_order output_order);

extern void fftw_mpi_die(const char *error_string);

/***********************************************************************/

typedef struct fftw_mpi_twiddle_struct {
     int rows, rowstart, cols, n;
     fftw_complex *W;
     int refcount;
     struct fftw_mpi_twiddle_struct *next;
} fftw_mpi_twiddle;

typedef struct fftw_mpi_plan_struct {
     int n, m, r, local_m, local_m_start, local_r, local_r_start;
     fftw_complex *fft_work;
     fftw_mpi_twiddle *tw;
     transpose_mpi_plan p_transpose, p_transpose_inv;
     fftw_plan pm, pr;
     int flags;
     fftw_direction dir;
} *fftw_mpi_plan;

/* new flags for the MPI planner: */
#define FFTW_SCRAMBLED_INPUT (8192)
#define FFTW_SCRAMBLED_OUTPUT (16384)

extern void fftw_mpi_local_sizes(fftw_mpi_plan p,
				 int *local_n,
				 int *local_start,
				 int *local_n_after_transform,
				 int *local_start_after_transform,
				 int *total_local_size);

extern fftw_mpi_plan fftw_mpi_create_plan(MPI_Comm comm,
					  int n,
					  fftw_direction dir, int flags);

extern void fftw_mpi_destroy_plan(fftw_mpi_plan p);

extern void fftw_mpi(fftw_mpi_plan p, int n_fields,
		     fftw_complex *local_data, fftw_complex *work);

extern void fftw_mpi_print_plan(fftw_mpi_plan p);

/***********************************************************************/

#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */

#endif /* FFTW_MPI_H */


#pragma once

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

class MaxOptimizationWrapper {
public:
	virtual gsl_vector* getMinState() = 0;
	virtual double getObjectiveVal() = 0;
	virtual bool maximize(Interface* inputs, const gsl_vector* initState, const gsl_vector* initDir, const set<int>& assertConstraints, int minimizeNode, float beta, int level, int id) = 0;
};