/**
* Drizzle utilities
*/

#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_statistics.h>
#include <math.h>

#include "aXe_grism.h"
#include "aXe_utils.h"
#include "spc_trace_functions.h"
#include "crossdisp_utils.h"
#include "aper_conf.h"
#include "trace_conf.h"
#include "disp_conf.h"
#include "spc_wl_calib.h"
#include "drizzle_utils.h"

d_point
get_refwave_position(dispstruct * disp, trace_func * trace, d_point refpix,
                     aperture_conf *conf)
{
  double a=0.0;
  double b, c, c_star;
  double *cf;
  double rot;
  double dx, dy, dtr;
  d_point res;

  // check for a dispersion solution
  // higher that quadratic order
  if (disp->pol->size > 3)
    // this can not be solved; give an error
    aXe_message(aXe_M_FATAL, __FILE__, __LINE__,
                "Order of dispersion solution: %i!\n"
                "At most qudratic solutions are allowed!\n",
                disp->pol->size-1);
  else if (disp->pol->size > 2)
    // store the quadratic term, if it exists
    a = gsl_vector_get(disp->pol, 2);  // ddlambda-term
  else
    // set the qudratic term to zero
    a=0.0;

  // store the constant and linear terms
  b = gsl_vector_get(disp->pol, 1);  // dlambda-term
  c = gsl_vector_get(disp->pol, 0);  // lambda0-term

  // store the dydx value
  cf = trace->data;

  // dydx -->  rotation angle
  rot = get_rotation_angle(cf[2]);

  // determine the wavelength difference
  c_star = c - conf->drz_lamb0;

  // transform the wavelength difference
  // into a path length difference
  if (a)
    dtr = (-1.0*b+sqrt(b*b - 4.0*a*c_star))/(2.0*a);
  else
    dtr = c_star/b;

  // path length difference --> dx, dy
  dx = dtr*cos(rot);
  dy = dtr*sin(rot);

  // compute the absolute values
  // for the reference position
  // in the stamp images
  res.x = refpix.x + dx;
  res.y = refpix.y + dy;

  // return the result
  return res;
}

double
get_drizzle_width(object *ob, int beamnum,trace_func * trace)
{
  double drizzle_width, drizzle_orient;
  double orig_width, orig_orient;
  double rotation, angle, oangle, factor;
  double *cf;
  beam *b = ob->beams+beamnum;

  cf = trace->data;
  rotation = get_rotation_angle(cf[2]);

  orig_orient = b->orient;
  orig_width  = b->width;

  drizzle_orient = orig_orient-rotation;

  angle  = drizzle_orient / M_PI * 180.;
  oangle = orig_orient / M_PI * 180.;

  drizzle_width = fabs(orig_width * sin (drizzle_orient));
  factor = drizzle_width/orig_width;

  if (angle > 180.0)
    angle = angle-180.0;
  if (angle < 30.0 || angle > 150.0)
     fprintf (stdout,
              "Angle: %4.0f --> %4.0f, Width: %5.1f --> %5.1f, %5.3f\n",
              oangle, angle, orig_width, drizzle_width, factor);

  return drizzle_width;
}

gsl_matrix  *
get_drizzle_coeffs(dispstruct * disp, trace_func * trace,
                   int boxwidth, int boxheight, int trlength,
                   double relx, double rely, aperture_conf *conf,
                   double orient, dispstruct * outdisp, double cdcorr,
                   double sprefreso, double spmeanreso)
{
  double a, b, c;
  double ao, bo, co, bref, cref;
  double a11, a12, a21, a22;
  double xr, yr;
  //double lambda0;
  //double dlambda;
  double tmp, rotation;
  double *cf;
  double shear_term;

  gsl_matrix * ret = gsl_matrix_alloc(2,11);
  gsl_matrix * rotcoeffs;

  gsl_matrix_set_all (ret, 0.0);

  /* get the dispersion at the objects point */
  if (disp->pol->size > 2)
    a = gsl_vector_get(disp->pol, 2);
    //    fprintf (stdout, "quadratic solution:  %f\n", a);
  else
    a = 0.0;

  b = gsl_vector_get(disp->pol, 1);  // dlambda-term
  c = gsl_vector_get(disp->pol, 0);  // lambda0-term

  /* get the dispersion at the mean reference point */
  if (outdisp->pol->size > 2){
    ao = gsl_vector_get(outdisp->pol, 2);
    //    fprintf (stdout, "quadratic solution:  %f, old: %f\n", a, ao);
  }
  else{
    ao = 0.0;
  }
  bo = gsl_vector_get(outdisp->pol, 1);  // dlambda-term
  co = gsl_vector_get(outdisp->pol, 0);  // lambda0-term
  if (conf->drz_resol == 0.0) {
    conf->drz_resol = bo + ao*((double)trlength)/2.0;
  }
  if (conf->drz_lamb0 == 0.0) {
    conf->drz_lamb0 = co;
  }

  //  fprintf (stdout, "lambda terms:  4785.0 <-> %f, 24.0 <-> %f\n", cref, bref);

  cref = conf->drz_lamb0;
  bref = conf->drz_resol;
  bref = sprefreso;

  // Bugfix on Sept. 15th 2010:
  // Check whether the ratio of the average dispersion on the grism images
  // and the dispersion on the drizzled images is outsidethe range 0.95 < ratio < 1.05.
  // If yes, adjust "trlength" which, from now on, is the length of the
  // drizzled image. This fix is necessary to allow a different sampling
  // in axedrizzle. The range was introduced such that the bug-fixed
  // version delivers identical results when using the default sampling.
  if ((spmeanreso / sprefreso > 1.05) || (spmeanreso / sprefreso < 0.95))
    trlength = (int)(spmeanreso / sprefreso * (double)trlength) + 0.5;

  /* put the below lines to go BACK to
   * the old computation of the length of
   * the aXedrizzled images:
  fprintf(stdout, "New spectral length: %i", trlength);
  trlength = (int)ceil(bo/bref*sqrt(pow((double)boxwidth,2.0)
                                    +pow((double)boxheight,2.0)));
  trlength = (int)ceil(spmeanreso/sprefreso*sqrt(pow((double)boxwidth,2.0)
                                                 +pow((double)boxheight,2.0)));
  fprintf(stdout, "<--> old spectral length: %i\n", trlength);
  */

  //fprintf (stdout,"Drizzled to resolution: %e, mean resolution for object: %e\n", sprefreso, spmeanreso);

  gsl_matrix_set(ret, 0,10,trlength);
  //  gsl_matrix_set(ret, 1,10,bref);

  cf = trace->data;
  rotation = get_rotation_angle(cf[2]);
  rotcoeffs = get_coeffs_back(trace);
  a11 = gsl_matrix_get(rotcoeffs,0,0);
  a12 = gsl_matrix_get(rotcoeffs,0,1);
  a21 = gsl_matrix_get(rotcoeffs,1,0);
  a22 = gsl_matrix_get(rotcoeffs,1,1);

  // the following lines have to be changed
  // in order to go from a integer
  // center definition to floating point center
  // definition.
  xr = (relx+1.0)-((double)(boxwidth/2)+1);  // +1.0 to compensate for "sp_sex.c" around line 537
  yr = (rely+1.0)-((double)(boxheight/2)+1); // +1.0 to compensate for "sp_sex.c" around line 537

  if (b < 0.0)
    shear_term = -tan(orient-rotation);
  else
    shear_term = tan(orient-rotation);

  // Transformations for X, without the lambda-terms, only rotation:
  //  tmp = xoffs-(((double)trlength/2.0)+1)-a11*xr-a12*yr;
  //  gsl_matrix_set(ret, 0,0,tmp);  // constant term

  //  tmp = a11;
  //  gsl_matrix_set(ret, 0,1,tmp);  // x-term

  //  tmp = a12;
  //  gsl_matrix_set(ret, 0,2,tmp);  // y-term
  //----------------------------------------------------------------------------------

  //------------------------------------------------------------------
  // Mathematica based run
  tmp = 1.0/bref*(c - cref - a11*b*xr + a*a11*a11*xr*xr - a12*b*yr + 2*a*a11*a12*xr*yr + a*a12*a12*yr*yr) +
    (a21*xr + a22*yr)/shear_term - ((double)(trlength/2)+1.0) + conf->drz_xstart;
  gsl_matrix_set(ret, 0,0,tmp);  // constant term

  tmp = 1.0/bref*(a11*b - 2*a*a11*a11*xr - 2*a*a11*a12*yr) - a21/shear_term;
  gsl_matrix_set(ret, 0,1,tmp);  // x-term

  tmp = 1.0/bref*(a12*b - 2*a*a12*a12*yr - 2*a*a11*a12*xr) - a22/shear_term;
  gsl_matrix_set(ret, 0,2,tmp);  // y-term

  tmp = a*a11*a11/bref;
  gsl_matrix_set(ret, 0,3,tmp);  // x^2-term

  tmp = 2*a*a11*a12/bref;
  gsl_matrix_set(ret, 0,4,tmp); ; // xy-term

  tmp = a*a12*a12/bref;
  gsl_matrix_set(ret, 0,5,tmp);  // y^2-term

  //------------------------------------------------------------------

  // Transformations for Y:
  tmp = -1.0*(a21*xr+a22*yr) * cdcorr; // -1.5  is some kind of a fudge factor, no idea where it comes from
  gsl_matrix_set(ret, 1,0,tmp);  // constant term

  tmp = a21 * cdcorr;
  gsl_matrix_set(ret, 1,1,tmp);  // x-term

  tmp = a22 * cdcorr;
  gsl_matrix_set(ret, 1,2,tmp);  // y-term

  gsl_matrix_free(rotcoeffs);

  return ret;
}

gsl_matrix *
get_coeffs_back(trace_func * trace)
{
  double *cf;
  double rotation;
  gsl_matrix * ret = gsl_matrix_alloc(2,2);

  //  ret = gsl_matrix_alloc (2,2);
  gsl_matrix_set_all (ret, 0.0);

  cf = trace->data;

  rotation = get_rotation_angle(cf[2]);

  gsl_matrix_set(ret, 0, 0,      cos(rotation));
  gsl_matrix_set(ret, 0, 1,      sin(rotation));
  gsl_matrix_set(ret, 1, 0, -1.0*sin(rotation));
  gsl_matrix_set(ret, 1, 1,      cos(rotation));

  return ret;
}

double
get_rotation_angle(double dxdy){
  double rotation=0.0;

  rotation = atan2(dxdy, 1.0);

  return rotation;
}

// Start functions for an alternative approach to store the object information
objectobs **
malloc_objectobs(){
  objectobs **allobjects;

  allobjects = (objectobs **) malloc (NMAXOBJ*sizeof(objectobs *));

  return allobjects;
}

void
free_objectobs(objectobs **allobjects){
  //  int i, nobjs=0;
  //  while (allobjects[nobjs])
  //    nobjs++;
  //  for (i=0; i<nobjs; i++){
  //      free(allobjects[i]);
  //      //    allobjects[i] = NULL;
  //  }
  free(allobjects);
}

int
add_observation(char * filename, char *  conf_file, objectobs ** allobjects, int nobjects,
                object ** oblist, int list_size, px_point pixmax, int sci_numext)
{

  int i, j, dec;
  int beamID = 0;
  int get_scale=0;
  int tlength;
  gsl_matrix * drzcoeffs;
  double cdscale;
  double drzscale;
  dispstruct * disp;
  calib_function *wl_calib;
  double l1, l2, spreso;

  drzcoeffs = get_crossdisp_matrix(filename, sci_numext);

  if (drzcoeffs->size1 > 1 && drzcoeffs->size2)
    get_scale = 1;

  if (get_scale){
    drzscale =  (double)get_float_from_keyword(filename, 1, "DRZSCALE");
  }
  for (i=0; i < list_size; i++){
    if (oblist[i]->beams[beamID].ignore == 0){
      dec = 0;

      if (get_scale)
        cdscale = drzscale * get_crossdisp_scale(oblist[i]->beams[beamID].spec_trace,
                                                 oblist[i]->beams[beamID].refpoint, drzcoeffs, pixmax);
      else
        cdscale=1.0;

      disp = get_dispstruct_at_pos(conf_file, 1, beamID,oblist[i]->beams[beamID].refpoint);
      wl_calib = create_calib_from_gsl_vector(1, disp->pol);
      l1 =  wl_calib->func (0.0,  wl_calib->order,  wl_calib->coeffs);
      l2 =  wl_calib->func (1.0,  wl_calib->order,  wl_calib->coeffs);
      spreso = fabs(l2-l1);

      // compute the tracelength
      tlength = get_beam_trace_length(oblist[i]->beams[beamID]);

      for (j=0; j < nobjects; j++){
        if (allobjects[j]->OBJID == oblist[i]->ID){
          add_obs_to_allobj(allobjects[j], oblist[i], pixmax, cdscale, spreso, tlength);
          dec = 1;
        }
      }
      if (dec == 0) {
        nobjects = add_obj_to_allobj(allobjects, nobjects, oblist[i], pixmax, cdscale, spreso, tlength);
      }
      free_dispstruct(disp);
      free_calib(wl_calib);
    }
  }
  gsl_matrix_free(drzcoeffs);
  return nobjects;
}

void
add_obs_to_allobj(objectobs *actobject, object * actobs, px_point pixmax, double cdscale, double spreso, int tlength)
{

  int xmin, xmax, ymin, ymax;
  //double m, b;
  gsl_vector_int * xvec;
  gsl_vector_int * yvec;
  px_point bbox;
  px_point mins;

  double *gaga;

  xvec = gsl_vector_int_alloc (4);
  yvec = gsl_vector_int_alloc (4);

  /* Store the refpoint */
  actobject->refpoint[actobject->nobs].x = actobs->beams[0].refpoint.x;
  actobject->refpoint[actobject->nobs].y = actobs->beams[0].refpoint.y;

  //*************************************************
  // patch to correct the reference point in case
  // that the the trace descritpion
  // does have a non negligeable first order term!
  gaga = actobs->beams[0].spec_trace->data;
  actobject->refpoint[actobject->nobs].y = actobject->refpoint[actobject->nobs].y + gaga[1];
  //*************************************************

  gsl_vector_int_set(xvec, 0, actobs->beams[0].corners[0].x);
  gsl_vector_int_set(xvec, 1, actobs->beams[0].corners[1].x);
  gsl_vector_int_set(xvec, 2, actobs->beams[0].corners[2].x);
  gsl_vector_int_set(xvec, 3, actobs->beams[0].corners[3].x);
  xmin = gsl_stats_int_min(xvec->data, 1, 4);
  xmax = gsl_stats_int_max(xvec->data, 1, 4);
  gsl_vector_int_set(yvec, 0, actobs->beams[0].corners[0].y);
  gsl_vector_int_set(yvec, 1, actobs->beams[0].corners[1].y);
  gsl_vector_int_set(yvec, 2, actobs->beams[0].corners[2].y);
  gsl_vector_int_set(yvec, 3, actobs->beams[0].corners[3].y);
  ymin = gsl_stats_int_min(yvec->data, 1, 4);
  ymax = gsl_stats_int_max(yvec->data, 1, 4);

  bbox = recalc_bbox(xmin, xmax, ymin, ymax, pixmax);
  mins = recalc_mins(xmin, xmax, ymin, ymax, pixmax);

  actobject->width[actobject->nobs]    = bbox.x;
  actobject->height[actobject->nobs]   = bbox.y;
  actobject->tlength[actobject->nobs]  = tlength;
  actobject->objwidth[actobject->nobs] = actobs->beams[0].width;
  actobject->orient[actobject->nobs]   = actobs->beams[0].orient;
  actobject->cdscale[actobject->nobs]   = cdscale;
  actobject->spreso[actobject->nobs]   = spreso;

  actobject->relrefpt[actobject->nobs].x = actobs->beams[0].refpoint.x - (double)mins.x;
  actobject->relrefpt[actobject->nobs].y = actobs->beams[0].refpoint.y - (double)mins.y;

  actobject->nobs = actobject->nobs + 1;

  gsl_vector_int_free(xvec);
  gsl_vector_int_free(yvec);
}

int
add_obj_to_allobj(objectobs **allobjects, int nobjects, object * actobs,
                   px_point pixmax, double cdscale, double spreso, int tlength)
{
  int xmin, xmax, ymin, ymax;
  //double m, b;

  gsl_vector_int * xvec;
  gsl_vector_int * yvec;

  px_point bbox;
  px_point mins;

  double *gaga;

  //  object *ob = malloc (sizeof (object));
  objectobs *objobs = malloc (sizeof (objectobs));

  xvec = gsl_vector_int_alloc (4);
  yvec = gsl_vector_int_alloc (4);

  allobjects[nobjects] = objobs;
  /* Store the refpoint */
  allobjects[nobjects]->refpoint[0].x = actobs->beams[0].refpoint.x;
  allobjects[nobjects]->refpoint[0].y = actobs->beams[0].refpoint.y;

  //*************************************************
  // patch to correct the reference point in case
  // that the the trace descritpion
  // does have a non negligeable first order term!
  gaga = actobs->beams[0].spec_trace->data;
  allobjects[nobjects]->refpoint[0].y = allobjects[nobjects]->refpoint[0].y + gaga[1];
  //*************************************************

  allobjects[nobjects]->OBJID = actobs->ID;
  allobjects[nobjects]->nobs = 1;
  allobjects[nobjects]->pointer = 0;

  gsl_vector_int_set(xvec, 0, actobs->beams[0].corners[0].x);
  gsl_vector_int_set(xvec, 1, actobs->beams[0].corners[1].x);
  gsl_vector_int_set(xvec, 2, actobs->beams[0].corners[2].x);
  gsl_vector_int_set(xvec, 3, actobs->beams[0].corners[3].x);
  xmin = gsl_stats_int_min(xvec->data, 1, 4);
  xmax = gsl_stats_int_max(xvec->data, 1, 4);

  gsl_vector_int_set(yvec, 0, actobs->beams[0].corners[0].y);
  gsl_vector_int_set(yvec, 1, actobs->beams[0].corners[1].y);
  gsl_vector_int_set(yvec, 2, actobs->beams[0].corners[2].y);
  gsl_vector_int_set(yvec, 3, actobs->beams[0].corners[3].y);
  ymin = gsl_stats_int_min(yvec->data, 1, 4);
  ymax = gsl_stats_int_max(yvec->data, 1, 4);

  bbox = recalc_bbox(xmin, xmax, ymin, ymax, pixmax);
  mins = recalc_mins(xmin, xmax, ymin, ymax, pixmax);

  allobjects[nobjects]->width[0]   = bbox.x;
  allobjects[nobjects]->height[0]  = bbox.y;
  allobjects[nobjects]->tlength[0] = tlength;
  allobjects[nobjects]->objwidth[0] = actobs->beams[0].width;
  allobjects[nobjects]->orient[0]   = actobs->beams[0].orient;
  allobjects[nobjects]->cdscale[0]  = cdscale;
  allobjects[nobjects]->spreso[0]   = spreso;

  allobjects[nobjects]->relrefpt[0].x = actobs->beams[0].refpoint.x - (double)mins.x;
  allobjects[nobjects]->relrefpt[0].y = actobs->beams[0].refpoint.y - (double)mins.y;

  ++nobjects;

  gsl_vector_int_free(xvec);
  gsl_vector_int_free(yvec);

  return nobjects;
}

void print_objectobs(objectobs **allobjects, int nobjects){
  int i, j;

  for (i = 0; i < nobjects; i++){
   fprintf(stdout, "OBJID: %i, no of observ.: %i\n", allobjects[i]->OBJID, allobjects[i]->nobs);
    for (j = 0; j < allobjects[i]->nobs; j++){
      fprintf(stdout, "No %i: width %f, height: %f, \n", j, allobjects[i]->relrefpt[j].x, allobjects[i]->relrefpt[j].y);
    }
  }
}
void print_objectobs2(objectobs allobjects[], int nobjects){
  int i, j;

  for (i = 0; i < nobjects; i++){
   fprintf(stdout, "OBJID: %i, no of observ.: %i\n", allobjects[i].OBJID, allobjects[i].nobs);
    for (j = 0; j < allobjects[i].nobs; j++){
      fprintf(stdout, "No %i: width %f, height: %f, \n", j, allobjects[i].relrefpt[j].x, allobjects[i].relrefpt[j].y);
    }
  }
}
int make_refpoints( char * conf_file, char * filename, px_point pixmax, objectobs **allobjects, int nobjects)
{
  int i, j;
  int nobs;
  int max_width, max_height, omax;
  double xmean, ymean;
  //double xdata[NMAXOBS];
  //double ydata[NMAXOBS];
  //  objectobs  oneobject;
  objectobs *oneobject; // = malloc (sizeof (objectobs));

  gsl_vector * xvec;
  gsl_vector * yvec;
  aperture_conf *conf;
  gsl_matrix * drzcoeffs;
  //double cdscale;
  double drzscale;
  trace_func  *trace;
  int beamID=0;
  dispstruct * disp;
  calib_function *wl_calib;
  double l1, l2;

  conf = get_aperture_descriptor(conf_file);
  get_extension_numbers(filename, conf,conf->optkey1,conf->optval1);
  drzcoeffs = get_crossdisp_matrix(filename,conf->science_numext);
  drzscale =  (double)get_float_from_keyword(filename, 1, "DRZSCALE");

  for (i=0; i < nobjects; i++){
    oneobject = allobjects[i];
    nobs = oneobject->nobs;

    xvec = gsl_vector_alloc (nobs);
    yvec = gsl_vector_alloc (nobs);
    for (j=0; j < nobs; j++){
      gsl_vector_set(xvec, j, oneobject->refpoint[j].x);
      gsl_vector_set(yvec, j, oneobject->refpoint[j].y);
    }
    xmean = gsl_stats_mean(xvec->data, 1, nobs);
    ymean = gsl_stats_mean(yvec->data, 1, nobs);

    max_width  = gsl_stats_int_max(oneobject->width,  1, nobs);
    max_height = gsl_stats_int_max(oneobject->height, 1, nobs);
    omax       = gsl_stats_max(oneobject->objwidth, 1, nobs);

    allobjects[i]->mean_refpoint.x = xmean;
    allobjects[i]->mean_refpoint.y = ymean;
    allobjects[i]->max_width  = max_width;
    allobjects[i]->max_height = max_height;
    allobjects[i]->owidthmax  = omax;

    allobjects[i]->max_tlength = gsl_stats_int_max(oneobject->tlength, 1, nobs);

    /*
     * Look whether the cross dispersion scale is given in the configuration.
     * If not determine the cross dispersion scale at the mean reference point an store it
     * store also the mean correction in for to correct the width.
     *
     */
    if (conf->drz_scale < 1.0e-16){
      trace = get_tracefunc_at(conf_file, allobjects[i]->mean_refpoint);
      allobjects[i]->cdrefscale = drzscale * get_crossdisp_scale(trace, allobjects[i]->mean_refpoint, drzcoeffs, pixmax);
    }
    else{
      allobjects[i]->cdrefscale = conf->drz_scale;
    }
    for (j=0; j < nobs; j++){
      gsl_vector_set(xvec, j, oneobject->cdscale[j]);
    }
    xmean = gsl_stats_mean(xvec->data, 1, nobs);
    allobjects[i]->cdmeanscale  = xmean;

    /*
     * look whether the wavelength dispersion is given in the conig file
     * if not determine the wavelength dispersion at the mean reference point amd store
     * it. Store also the mean wavelength dispersion to correct the length
     */
    if (conf->drz_resol < 1.0e-16){
      disp = get_dispstruct_at_pos(conf_file, 1, beamID, allobjects[i]->mean_refpoint);
      wl_calib = create_calib_from_gsl_vector(1, disp->pol);
      l1 =  wl_calib->func (0.0,  wl_calib->order,  wl_calib->coeffs);
      l2 =  wl_calib->func (1.0,  wl_calib->order,  wl_calib->coeffs);
      allobjects[i]->sprefreso = fabs(l2-l1);
    }
    else{
       allobjects[i]->sprefreso = conf->drz_resol;
    }
    for (j=0; j < nobs; j++){
      gsl_vector_set(xvec, j, oneobject->spreso[j]);
    }
    xmean = gsl_stats_mean(xvec->data, 1, nobs);
    allobjects[i]->spmeanreso  = xmean;

    gsl_vector_free(xvec);
    gsl_vector_free(yvec);
  }

  gsl_matrix_free(drzcoeffs);
  free(conf);

  return 0;
}

d_point
get_mean_refpoint(objectobs **allobjects, int nobjects, int ID,int * boxwidth,
                  int * boxheight, double * relx, double * rely,
                  double * objwidth, double *orient, double * cdref,
                  double *cdscale, double *cdmeanscale, double *sprefreso, double *spreso,
                  double *spmeanreso, int *tlength)
{
  int i, dec=0;
  d_point mpoint;

  for (i=0; i < nobjects; i++){
    if (allobjects[i]->OBJID == ID){
      mpoint.x = allobjects[i]->mean_refpoint.x;
      mpoint.y = allobjects[i]->mean_refpoint.y;
      *boxwidth  = allobjects[i]->max_width;
      *boxheight = allobjects[i]->max_height;
      *tlength   = allobjects[i]->max_tlength;
      *objwidth  = allobjects[i]->owidthmax;
      *cdref     = allobjects[i]->cdrefscale;
      *cdmeanscale     = allobjects[i]->cdmeanscale;
      *spmeanreso= allobjects[i]->spmeanreso;
      *sprefreso = allobjects[i]->sprefreso;
      *orient    = allobjects[i]->orient[allobjects[i]->pointer];
      *cdscale   = allobjects[i]->cdscale[allobjects[i]->pointer];
      *spreso    = allobjects[i]->spreso[allobjects[i]->pointer];
      *relx      = allobjects[i]->relrefpt[allobjects[i]->pointer].x;
      *rely      = allobjects[i]->relrefpt[allobjects[i]->pointer].y;
      ++allobjects[i]->pointer;
      dec=1;
    }
  }
  return mpoint;
}
px_point
recalc_bbox(int xmin, int xmax, int ymin, int ymax, px_point pixmax)
{
  px_point ret;

  if (xmax > pixmax.x)
    xmax = pixmax.x;
  if (ymax > pixmax.y)
    ymax = pixmax.y;
  if (xmin < 1)
    xmin = 1;
  if (ymin < 1)
    ymin = 1;

  ret.x = xmax - xmin + 2;
  ret.y = ymax - ymin + 2;

  return ret;
}
/*
I am not sure what this routine is doing,
but one should make it better.
*/
px_point
recalc_mins(int xmin, int xmax, int ymin, int ymax, px_point pixmax)
{
  double m, b;
  px_point ret;

  m = ((double)ymin - (double)ymax)/((double)xmax - (double)xmin);
  b = (double)ymin - m * (double)xmax;
  if (xmin < 1){
    ymax = (int)ceil(1.0*m + b);
    xmin=1;
  }
  if (xmax > pixmax.x){
    ymin = (int)ceil((float)pixmax.x*m + b);
    xmax = pixmax.x;
  }
  if (ymin < 1){
    xmax = (int)ceil((1-b)/m);
    ymin=1;
  }
  if (ymax > pixmax.y){
    xmin = (int)ceil(((float)pixmax.y-b)/m);
    ymax=pixmax.y;
  }
  ret.x = xmin;
  ret.y = ymin;

  return ret;
}

int
get_beam_trace_length(const beam actbeam)
{
  int trlength=0;
  double diagonal_1 = 0.0;
  double diagonal_2 = 0.0;
  double xdiff, ydiff;

  // compute the x- and y- differences for one corner pair
  xdiff = (float)actbeam.corners[0].x - (float)actbeam.corners[2].x;
  ydiff = (float)actbeam.corners[0].y - (float)actbeam.corners[2].y;

  // compute the diagonal distance
  diagonal_1 = xdiff*xdiff + ydiff*ydiff;

  // compute the x- and y- differences for the other corner pair
  xdiff = (float)actbeam.corners[1].x - (float)actbeam.corners[3].x;
  ydiff = (float)actbeam.corners[1].y - (float)actbeam.corners[3].y;

  // compute the diagonal distance
  diagonal_2 = xdiff*xdiff + ydiff*ydiff;

  // compute the tracelength
  // from the larger diagonal
  if (diagonal_1 > diagonal_2)
    trlength = (int)ceil(sqrt(diagonal_1)+3.0);
  else
    trlength = (int)ceil(sqrt(diagonal_2)+3.0);

  // return the result
  return trlength;
}


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

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

#include "ccl.h"

#ifdef HAVE_ANGPOW
#include "Angpow/angpow_ccl.h"
#endif

#define CCL_FRAC_RELEVANT 5E-4
//#define CCL_FRAC_RELEVANT 1E-3
//Gets the x-interval where the values of y are relevant
//(meaning, that the values of y for those x are at least above a fraction frac of its maximum)
static void get_support_interval(int n,double *x,double *y,double frac,
				 double *xmin_out,double *xmax_out)
{
  int ix;
  double ythr=-1000;

  //Initialize as the original edges in case we don't find an interval
  *xmin_out=x[0];
  *xmax_out=x[n-1];

  //Find threshold
  for(ix=0;ix<n;ix++) {
    if(y[ix]>ythr) ythr=y[ix];
  }
  ythr*=frac;

  //Find minimum
  for(ix=0;ix<n;ix++) {
    if(y[ix]>=ythr) {
      *xmin_out=x[ix];
      break;
    }
  }

  //Find maximum
  for(ix=n-1;ix>=0;ix--) {
    if(y[ix]>=ythr) {
      *xmax_out=x[ix];
      break;
    }
  }
}

//Wrapper around spline_eval with GSL function syntax
static double speval_bis(double x,void *params)
{
  return ccl_spline_eval(x,(SplPar *)params);
}

void ccl_cl_workspace_free(CCL_ClWorkspace *w)
{
  free(w->l_arr);
  free(w);
}

CCL_ClWorkspace *ccl_cl_workspace_new(int lmax,int l_limber,
				      double l_logstep,int l_linstep,int *status)
{
  int i_l,l0,increment;
  CCL_ClWorkspace *w=(CCL_ClWorkspace *)malloc(sizeof(CCL_ClWorkspace));
  if(w==NULL)
    *status=CCL_ERROR_MEMORY;

  if(*status==0) {
    //Set params
    w->lmax=lmax;
    w->l_limber=l_limber;
    w->l_logstep=l_logstep;
    w->l_linstep=l_linstep;

    //Compute number of multipoles
    i_l=0; l0=0;
    increment=CCL_MAX(((int)(l0*(w->l_logstep-1.))),1);
    while((l0 < w->lmax) && (increment < w->l_linstep)) {
      i_l++;
      l0+=increment;
      increment=CCL_MAX(((int)(l0*(w->l_logstep-1))),1);
    }
    increment=w->l_linstep;
    while(l0 < w->lmax) {
      i_l++;
      l0+=increment;
    }

    //Allocate array of multipoles
    w->n_ls=i_l+1;
    w->l_arr=(int *)malloc(w->n_ls*sizeof(int));
    if(w->l_arr==NULL)
      *status=CCL_ERROR_MEMORY;
  }

  if(*status==0) {
    //Redo the computation above and store values of ell
    i_l=0; l0=0;
    increment=CCL_MAX(((int)(l0*(w->l_logstep-1.))),1);
    while((l0 < w->lmax) && (increment < w->l_linstep)) {
      w->l_arr[i_l]=l0;
      i_l++;
      l0+=increment;
      increment=CCL_MAX(((int)(l0*(w->l_logstep-1))),1);
    }
    increment=w->l_linstep;
    while(l0 < w->lmax) {
      w->l_arr[i_l]=l0;
      i_l++;
      l0+=increment;
    }
    //Don't go further than lmaw
    w->l_arr[w->n_ls-1]=w->lmax;
  }

  return w;
}

CCL_ClWorkspace *ccl_cl_workspace_new_limber(int lmax,double l_logstep,int l_linstep,int *status)
{
  return ccl_cl_workspace_new(lmax,-1,l_logstep,l_linstep,status);
}

//Params for lensing kernel integrand
typedef struct {
  double chi;
  SplPar *spl_pz;
  ccl_cosmology *cosmo;
  int *status;
} IntLensPar;

//Integrand for lensing kernel
static double integrand_wl(double chip,void *params)
{
  IntLensPar *p=(IntLensPar *)params;
  double chi=p->chi;
  double a=ccl_scale_factor_of_chi(p->cosmo,chip, p->status);
  double z=1./a-1;
  double pz=ccl_spline_eval(z,p->spl_pz);
  double h=p->cosmo->params.h*ccl_h_over_h0(p->cosmo,a, p->status)/CLIGHT_HMPC;

  if(chi==0)
    return h*pz;
  else
    return h*pz*ccl_sinn(p->cosmo,chip-chi,p->status)/ccl_sinn(p->cosmo,chip,p->status);
}

//Integral to compute lensing window function
//chi     -> comoving distance
//cosmo   -> ccl_cosmology object
//spl_pz  -> normalized N(z) spline
//chi_max -> maximum comoving distance to which the integral is computed
//win     -> result is stored here
static int window_lensing(double chi,ccl_cosmology *cosmo,SplPar *spl_pz,double chi_max,double *win)
{
  int gslstatus =0, status =0;
  double result,eresult;
  IntLensPar ip;
  gsl_function F;
  gsl_integration_workspace *w=gsl_integration_workspace_alloc(ccl_gsl->N_ITERATION);

  ip.chi=chi;
  ip.cosmo=cosmo;
  ip.spl_pz=spl_pz;
  ip.status = &status;
  F.function=&integrand_wl;
  F.params=&ip;
  // This conputes the lensing kernel:
  //   w_L(chi) = Integral[ dN/dchi(chi') * f(chi'-chi)/f(chi') , chi < chi' < chi_horizon ]
  // Where f(chi) is the comoving angular distance (which is just chi for zero curvature).
  gslstatus=gsl_integration_qag(&F, chi, chi_max, 0,
                                ccl_gsl->INTEGRATION_EPSREL, ccl_gsl->N_ITERATION,
                                ccl_gsl->INTEGRATION_GAUSS_KRONROD_POINTS,
                                w, &result, &eresult);
  *win=result;
  gsl_integration_workspace_free(w);
  if(gslstatus!=GSL_SUCCESS || *ip.status) {
    ccl_raise_gsl_warning(gslstatus, "ccl_cls.c: window_lensing():");
    return 1;
  }
  //TODO: chi_max should be changed to chi_horizon
  //we should precompute this quantity and store it in cosmo by default

  return 0;
}

//Params for lensing kernel integrand
typedef struct {
  double chi;
  SplPar *spl_pz;
  SplPar *spl_sz;
  ccl_cosmology *cosmo;
  int *status;
} IntMagPar;

//Integrand for magnification kernel
static double integrand_mag(double chip,void *params)
{
  IntMagPar *p=(IntMagPar *)params;
  double chi=p->chi;
  double a=ccl_scale_factor_of_chi(p->cosmo,chip, p->status);
  double z=1./a-1;
  double pz=ccl_spline_eval(z,p->spl_pz);
  double sz=ccl_spline_eval(z,p->spl_sz);
  double h=p->cosmo->params.h*ccl_h_over_h0(p->cosmo,a, p->status)/CLIGHT_HMPC;

  if(chi==0)
    return h*pz*(1-2.5*sz);
  else
    return h*pz*(1-2.5*sz)*ccl_sinn(p->cosmo,chip-chi,p->status)/ccl_sinn(p->cosmo,chip,p->status);
}

//Integral to compute magnification window function
//chi     -> comoving distance
//cosmo   -> ccl_cosmology object
//spl_pz  -> normalized N(z) spline
//spl_pz  -> magnification bias s(z)
//chi_max -> maximum comoving distance to which the integral is computed
//win     -> result is stored here
static int window_magnification(double chi,ccl_cosmology *cosmo,SplPar *spl_pz,SplPar *spl_sz,
				double chi_max,double *win)
{
  int gslstatus =0, status =0;
  double result,eresult;
  IntMagPar ip;
  gsl_function F;
  gsl_integration_workspace *w=gsl_integration_workspace_alloc(ccl_gsl->N_ITERATION);

  ip.chi=chi;
  ip.cosmo=cosmo;
  ip.spl_pz=spl_pz;
  ip.spl_sz=spl_sz;
  ip.status = &status;
  F.function=&integrand_mag;
  F.params=&ip;
  // This conputes the magnification lensing kernel:
  //   w_M(chi) = Integral[ dN/dchi(chi') * (1-5/2 * s(chi)) * f(chi'-chi)/f(chi') , chi < chi' < chi_horizon ]
  // Where f(chi) is the comoving angular distance (which is just chi for zero curvature)
  // and s(chi) is the magnification bias parameter.
  gslstatus=gsl_integration_qag(&F, chi, chi_max, 0,
                                ccl_gsl->INTEGRATION_EPSREL, ccl_gsl->N_ITERATION,
                                ccl_gsl->INTEGRATION_GAUSS_KRONROD_POINTS,
                                w, &result, &eresult);
  *win=result;
  gsl_integration_workspace_free(w);
  if(gslstatus!=GSL_SUCCESS || *ip.status) {
    ccl_raise_gsl_warning(gslstatus, "ccl_cls.c: window_magnification():");
    return 1;
  }
  //TODO: chi_max should be changed to chi_horizon
  //we should precompute this quantity and store it in cosmo by default

  return 0;
}

static void clt_init_nz(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int nz_n,double *z_n,double *n,int *status)
{
  int gslstatus;
  gsl_function F;
  double nz_norm,nz_enorm;
  double *nz_normalized;

  //Find redshift range where the N(z) has support
  get_support_interval(nz_n,z_n,n,CCL_FRAC_RELEVANT,&(clt->zmin),&(clt->zmax));
  clt->chimax=ccl_comoving_radial_distance(cosmo,1./(1+clt->zmax),status);
  clt->chimin=ccl_comoving_radial_distance(cosmo,1./(1+clt->zmin),status);
  clt->spl_nz=ccl_spline_init(nz_n,z_n,n,0,0);
  if(clt->spl_nz==NULL) {
    *status=CCL_ERROR_SPLINE;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_nz(): error initializing spline for N(z)\n");
  }

  if(*status==0) {
    //Normalize n(z)
    nz_normalized=(double *)malloc(nz_n*sizeof(double));
    if(nz_normalized==NULL) {
      *status=CCL_ERROR_MEMORY;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_nz(): memory allocation\n");
      return;
    }
  }

  if(*status==0) {
    gsl_integration_workspace *w=gsl_integration_workspace_alloc(ccl_gsl->N_ITERATION);
    F.function=&speval_bis;
    F.params=clt->spl_nz;
    //Here we're just integrating the N(z) to normalize it to unit probability.
    gslstatus=gsl_integration_qag(&F, z_n[0], z_n[nz_n-1], 0,
				  ccl_gsl->INTEGRATION_EPSREL, ccl_gsl->N_ITERATION,
				  ccl_gsl->INTEGRATION_GAUSS_KRONROD_POINTS,
				  w, &nz_norm, &nz_enorm);
    gsl_integration_workspace_free(w);
    if(gslstatus!=GSL_SUCCESS) {
      ccl_raise_gsl_warning(gslstatus, "ccl_cls.c: clt_init_nz():");
      *status=CCL_ERROR_INTEG;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_nz(): integration error when normalizing N(z)\n");
    }
  }

  if(*status==0) {
    for(int ii=0;ii<nz_n;ii++)
      nz_normalized[ii]=n[ii]/nz_norm;
    ccl_spline_free(clt->spl_nz);
    clt->spl_nz=ccl_spline_init(nz_n,z_n,nz_normalized,0,0);
    if(clt->spl_nz==NULL) {
      *status=CCL_ERROR_SPLINE;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_nz(): error initializing normalized spline for N(z)\n");
    }
  }

  free(nz_normalized);
}

static void clt_init_bz(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int nz_b,double *z_b,double *b,int *status)
{
  //Initialize bias spline
  clt->spl_bz=ccl_spline_init(nz_b,z_b,b,b[0],b[nz_b-1]);
  if(clt->spl_bz==NULL) {
    *status=CCL_ERROR_SPLINE;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_bz(): error initializing spline for b(z)\n");
  }
}

static void clt_init_wM(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int nz_s,double *z_s,double *s,int *status)
{
  //Compute magnification kernel
  int nchi;
  double *x,*y;
  double dchi_here=5.;
  double zmax=clt->spl_nz->xf;
  double chimax=ccl_comoving_radial_distance(cosmo,1./(1+zmax),status);
  //TODO: The interval in chi (5. Mpc) should be made a macro

  //In this case we need to integrate all the way to z=0. Reset zmin and chimin
  clt->zmin=0;
  clt->chimin=0;
  clt->spl_sz=ccl_spline_init(nz_s,z_s,s,s[0],s[nz_s-1]);
  if(clt->spl_sz==NULL) {
    *status=CCL_ERROR_SPLINE;
    ccl_cosmology_set_status_message(cosmo,
				     "ccl_cls.c: clt_init_wM(): error initializing spline for s(z)\n");
  }

  if(*status==0) {
    nchi=(int)(chimax/dchi_here)+1;
    x=ccl_linear_spacing(0.,chimax,nchi);
    dchi_here=chimax/nchi;
    if(x==NULL || (fabs(x[0]-0)>1E-5) || (fabs(x[nchi-1]-chimax)>1e-5)) {
      *status=CCL_ERROR_LINSPACE;
      ccl_cosmology_set_status_message(cosmo,
				       "ccl_cls.c: clt_init_wM(): Error creating linear spacing in chi\n");
    }
  }

  if(*status==0) {
    y=(double *)malloc(nchi*sizeof(double));
    if(y==NULL) {
      *status=CCL_ERROR_MEMORY;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_wM(): memory allocation\n");
    }
  }

  if(*status==0) {
    int clstatus=0;
    for(int j=0;j<nchi;j++)
      clstatus|=window_magnification(x[j],cosmo,clt->spl_nz,clt->spl_sz,chimax,&(y[j]));
    if(clstatus) {
      *status=CCL_ERROR_INTEG;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_wM(): error computing lensing window\n");
    }
  }

  if(*status==0) {
    clt->spl_wM=ccl_spline_init(nchi,x,y,y[0],0);
    if(clt->spl_wM==NULL) {
      *status=CCL_ERROR_SPLINE;
      ccl_cosmology_set_status_message(cosmo,
				       "ccl_cls.c: clt_init_wM(): error initializing spline for lensing window\n");
    }
  }
  free(x); free(y);
}

//CCL_ClTracer initializer for number counts
static void clt_nc_init(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int has_rsd,int has_magnification,
			int nz_n,double *z_n,double *n,
			int nz_b,double *z_b,double *b,
			int nz_s,double *z_s,double *s,int *status)
{
  clt->has_rsd=has_rsd;
  clt->has_magnification=has_magnification;

  if ( ((cosmo->params.N_nu_mass)>0) && clt->has_rsd){
    *status=CCL_ERROR_NOT_IMPLEMENTED;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_cl_tracer_new(): Number counts tracers with RSD not yet implemented in cosmologies with massive neutrinos.");
    return;
  }

  clt_init_nz(clt,cosmo,nz_n,z_n,n,status);
  clt_init_bz(clt,cosmo,nz_b,z_b,b,status);
  if(clt->has_magnification)
    clt_init_wM(clt,cosmo,nz_s,z_s,s,status);
}

static void clt_init_wL(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int *status)
{
  //Compute weak lensing kernel
  int nchi;
  double *x,*y;
  double dchi_here=5.;
  double zmax=clt->spl_nz->xf;
  double chimax=ccl_comoving_radial_distance(cosmo,1./(1+zmax),status);
  //TODO: The interval in chi (5. Mpc) should be made a macro

  //In this case we need to integrate all the way to z=0. Reset zmin and chimin
  clt->zmin=0;
  clt->chimin=0;
  nchi=(int)(chimax/dchi_here)+1;
  x=ccl_linear_spacing(0.,chimax,nchi);
  dchi_here=chimax/nchi;
  if(x==NULL || (fabs(x[0]-0)>1E-5) || (fabs(x[nchi-1]-chimax)>1e-5)) {
    *status=CCL_ERROR_LINSPACE;
    ccl_cosmology_set_status_message(cosmo,
				     "ccl_cls.c: clt_init_wL(): Error creating linear spacing in chi\n");
  }

  if(*status==0) {
    y=(double *)malloc(nchi*sizeof(double));
    if(y==NULL) {
      *status=CCL_ERROR_MEMORY;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_wL(): memory allocation\n");
    }
  }

  if(*status==0) {
    int clstatus=0;
    for(int j=0;j<nchi;j++)
      clstatus|=window_lensing(x[j],cosmo,clt->spl_nz,chimax,&(y[j]));
    if(clstatus) {
      *status=CCL_ERROR_INTEG;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_wL(): error computing lensing window\n");
    }
  }

  if(*status==0) {
    clt->spl_wL=ccl_spline_init(nchi,x,y,y[0],0);
    if(clt->spl_wL==NULL) {
      *status=CCL_ERROR_SPLINE;
      ccl_cosmology_set_status_message(cosmo,
				     "ccl_cls.c: clt_init_wL(): error initializing spline for lensing window\n");
    }
  }
  free(x); free(y);
}

static void clt_init_rf(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int nz_rf,double *z_rf,double *rf,int *status)
{
  //Initialize bias spline
  clt->spl_rf=ccl_spline_init(nz_rf,z_rf,rf,rf[0],rf[nz_rf-1]);
  if(clt->spl_rf==NULL) {
    *status=CCL_ERROR_SPLINE;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_rf(): error initializing spline for b(z)\n");
  }
}

static void clt_init_ba(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int nz_ba,double *z_ba,double *ba,int *status)
{
  //Initialize bias spline
  clt->spl_ba=ccl_spline_init(nz_ba,z_ba,ba,ba[0],ba[nz_ba-1]);
  if(clt->spl_ba==NULL) {
    *status=CCL_ERROR_SPLINE;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: clt_init_ba(): error initializing spline for b(z)\n");
  }
}

static void clt_wl_init(CCL_ClTracer *clt,ccl_cosmology *cosmo,
			int has_intrinsic_alignment,
			int nz_n,double *z_n,double *n,
			int nz_ba,double *z_ba,double *ba,
			int nz_rf,double *z_rf,double *rf,int *status)
{
  clt->has_intrinsic_alignment=has_intrinsic_alignment;

  clt_init_nz(clt,cosmo,nz_n,z_n,n,status);
  clt_init_wL(clt,cosmo,status);
  if(clt->has_intrinsic_alignment) {
    clt_init_rf(clt,cosmo,nz_rf,z_rf,rf,status);
    clt_init_ba(clt,cosmo,nz_ba,z_ba,ba,status);
  }
}

//CCL_ClTracer creator
//cosmo   -> ccl_cosmology object
//tracer_type -> type of tracer. Supported: ccl_number_counts_tracer, ccl_weak_lensing_tracer
//nz_n -> number of points for N(z)
//z_n  -> array of z-values for N(z)
//n    -> corresponding N(z)-values. Normalization is irrelevant
//        N(z) will be set to zero outside the range covered by z_n
//nz_b -> number of points for b(z)
//z_b  -> array of z-values for b(z)
//b    -> corresponding b(z)-values.
//        b(z) will be assumed constant outside the range covered by z_n
static CCL_ClTracer *cl_tracer(ccl_cosmology *cosmo,int tracer_type,
			       int has_rsd,int has_magnification,int has_intrinsic_alignment,
			       int nz_n,double *z_n,double *n,
			       int nz_b,double *z_b,double *b,
			       int nz_s,double *z_s,double *s,
			       int nz_ba,double *z_ba,double *ba,
			       int nz_rf,double *z_rf,double *rf,
			       double z_source, int * status)
{
  int clstatus=0;
  CCL_ClTracer *clt=(CCL_ClTracer *)malloc(sizeof(CCL_ClTracer));
  if(clt==NULL) {
    *status=CCL_ERROR_MEMORY;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_cl_tracer(): memory allocation\n");
  }

  if(*status==0) {
    clt->tracer_type=tracer_type;

    double hub=cosmo->params.h*ccl_h_over_h0(cosmo,1.,status)/CLIGHT_HMPC;
    clt->prefac_lensing=1.5*hub*hub*cosmo->params.Omega_m;

    if(tracer_type==ccl_number_counts_tracer)
      clt_nc_init(clt,cosmo,has_rsd,has_magnification,
		  nz_n,z_n,n,nz_b,z_b,b,nz_s,z_s,s,status);
    else if(tracer_type==ccl_weak_lensing_tracer)
      clt_wl_init(clt,cosmo,has_intrinsic_alignment,
		  nz_n,z_n,n,nz_ba,z_ba,ba,nz_rf,z_rf,rf,status);
    else if(tracer_type==ccl_cmb_lensing_tracer) {
      clt->chi_source=ccl_comoving_radial_distance(cosmo,1./(1+z_source),status);
      clt->chimax=clt->chi_source;
      clt->chimin=0;
    }
    else {
      free(clt);
      *status=CCL_ERROR_INCONSISTENT;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_cl_tracer(): unknown tracer type\n");
      return NULL;
    }
  }

  if(*status) {
    free(clt);
    clt=NULL;
  }

  return clt;
}

//CCL_ClTracer constructor with error checking
//cosmo   -> ccl_cosmology object
//tracer_type -> type of tracer. Supported: ccl_number_counts_tracer, ccl_weak_lensing_tracer
//nz_n -> number of points for N(z)
//z_n  -> array of z-values for N(z)
//n    -> corresponding N(z)-values. Normalization is irrelevant
//        N(z) will be set to zero outside the range covered by z_n
//nz_b -> number of points for b(z)
//z_b  -> array of z-values for b(z)
//b    -> corresponding b(z)-values.
//        b(z) will be assumed constant outside the range covered by z_n
CCL_ClTracer *ccl_cl_tracer(ccl_cosmology *cosmo,int tracer_type,
				int has_rsd,int has_magnification,int has_intrinsic_alignment,
				int nz_n,double *z_n,double *n,
				int nz_b,double *z_b,double *b,
				int nz_s,double *z_s,double *s,
				int nz_ba,double *z_ba,double *ba,
				int nz_rf,double *z_rf,double *rf,
				double z_source, int * status)
{
  CCL_ClTracer *clt=cl_tracer(cosmo,tracer_type,has_rsd,has_magnification,has_intrinsic_alignment,
			      nz_n,z_n,n,nz_b,z_b,b,nz_s,z_s,s,
			      nz_ba,z_ba,ba,nz_rf,z_rf,rf,z_source,status);
  ccl_check_status(cosmo,status);
  return clt;
}

//CCL_ClTracer destructor
void ccl_cl_tracer_free(CCL_ClTracer *clt)
{
  if((clt->tracer_type==ccl_number_counts_tracer) || (clt->tracer_type==ccl_weak_lensing_tracer))
    ccl_spline_free(clt->spl_nz);

  if(clt->tracer_type==ccl_number_counts_tracer) {
    ccl_spline_free(clt->spl_bz);
    if(clt->has_magnification) {
      ccl_spline_free(clt->spl_sz);
      ccl_spline_free(clt->spl_wM);
    }
  }
  else if(clt->tracer_type==ccl_weak_lensing_tracer) {
    ccl_spline_free(clt->spl_wL);
    if(clt->has_intrinsic_alignment) {
      ccl_spline_free(clt->spl_ba);
      ccl_spline_free(clt->spl_rf);
    }
  }
  free(clt);
}

CCL_ClTracer *ccl_cl_tracer_cmblens(ccl_cosmology *cosmo,double z_source,int *status)
{
  return ccl_cl_tracer(cosmo,ccl_cmb_lensing_tracer,
			   0,0,0,
			   0,NULL,NULL,0,NULL,NULL,0,NULL,NULL,
			   0,NULL,NULL,0,NULL,NULL,z_source,status);
}

CCL_ClTracer *ccl_cl_tracer_number_counts(ccl_cosmology *cosmo,
					      int has_rsd,int has_magnification,
					      int nz_n,double *z_n,double *n,
					      int nz_b,double *z_b,double *b,
					      int nz_s,double *z_s,double *s, int * status)
{
  return ccl_cl_tracer(cosmo,ccl_number_counts_tracer,has_rsd,has_magnification,0,
			   nz_n,z_n,n,nz_b,z_b,b,nz_s,z_s,s,
			   -1,NULL,NULL,-1,NULL,NULL,0, status);
}

CCL_ClTracer *ccl_cl_tracer_number_counts_simple(ccl_cosmology *cosmo,
						     int nz_n,double *z_n,double *n,
						     int nz_b,double *z_b,double *b, int * status)
{
  return ccl_cl_tracer(cosmo,ccl_number_counts_tracer,0,0,0,
			   nz_n,z_n,n,nz_b,z_b,b,-1,NULL,NULL,
			   -1,NULL,NULL,-1,NULL,NULL,0, status);
}

CCL_ClTracer *ccl_cl_tracer_lensing(ccl_cosmology *cosmo,
					int has_alignment,
					int nz_n,double *z_n,double *n,
					int nz_ba,double *z_ba,double *ba,
					int nz_rf,double *z_rf,double *rf, int * status)
{
  return ccl_cl_tracer(cosmo,ccl_weak_lensing_tracer,0,0,has_alignment,
			   nz_n,z_n,n,-1,NULL,NULL,-1,NULL,NULL,
			   nz_ba,z_ba,ba,nz_rf,z_rf,rf,0, status);
}

CCL_ClTracer *ccl_cl_tracer_lensing_simple(ccl_cosmology *cosmo,
					       int nz_n,double *z_n,double *n, int * status)
{
  return ccl_cl_tracer(cosmo,ccl_weak_lensing_tracer,0,0,0,
			   nz_n,z_n,n,-1,NULL,NULL,-1,NULL,NULL,
			   -1,NULL,NULL,-1,NULL,NULL,0, status);
}

static double f_dens(double a,ccl_cosmology *cosmo,CCL_ClTracer *clt, int * status)
{
  double z=1./a-1;
  double pz=ccl_spline_eval(z,clt->spl_nz);
  double bz=ccl_spline_eval(z,clt->spl_bz);
  double h=cosmo->params.h*ccl_h_over_h0(cosmo,a,status)/CLIGHT_HMPC;

  return pz*bz*h;
}

static double f_rsd(double a,ccl_cosmology *cosmo,CCL_ClTracer *clt, int * status)
{
  double z=1./a-1;
  double pz=ccl_spline_eval(z,clt->spl_nz);
  double fg=ccl_growth_rate(cosmo,a,status);
  double h=cosmo->params.h*ccl_h_over_h0(cosmo,a,status)/CLIGHT_HMPC;

  return pz*fg*h;
}

static double f_mag(double a,double chi,ccl_cosmology *cosmo,CCL_ClTracer *clt, int * status)
{
  double wM=ccl_spline_eval(chi,clt->spl_wM);

  if(wM<=0)
    return 0;
  else
    return wM/(a*chi);
}

//Transfer function for number counts
//l -> angular multipole
//k -> wavenumber modulus
//cosmo -> ccl_cosmology object
//w -> CCL_ClWorskpace object
//clt -> CCL_ClTracer object (must be of the ccl_number_counts_tracer type)
static double transfer_nc(int l,double k,
			  ccl_cosmology *cosmo,CCL_ClWorkspace *w,CCL_ClTracer *clt, int * status)
{
  double ret=0;
  double x0=(l+0.5);
  double chi0=x0/k;
  if(chi0<=clt->chimax) {
    double a0=ccl_scale_factor_of_chi(cosmo,chi0,status);
    double f_all=f_dens(a0,cosmo,clt,status);
    if(clt->has_rsd) {
      double x1=(l+1.5);
      double chi1=x1/k;
      if(chi1<=clt->chimax) {
	double a1=ccl_scale_factor_of_chi(cosmo,chi1,status);
	double pk0=ccl_nonlin_matter_power(cosmo,k,a0,status);
	double pk1=ccl_nonlin_matter_power(cosmo,k,a1,status);
	double fg0=f_rsd(a0,cosmo,clt,status);
	double fg1=f_rsd(a1,cosmo,clt,status);
	f_all+=fg0*(1.-l*(l-1.)/(x0*x0))-fg1*2.*sqrt((l+0.5)*pk1/((l+1.5)*pk0))/x1;
      }
    }
    if(clt->has_magnification)
      f_all+=-2*clt->prefac_lensing*l*(l+1)*f_mag(a0,chi0,cosmo,clt,status)/(k*k);
    ret=f_all;
  }

  return ret;
}

static double f_lensing(double a,double chi,ccl_cosmology *cosmo,CCL_ClTracer *clt, int * status)
{
  double wL=ccl_spline_eval(chi,clt->spl_wL);

  if(wL<=0)
    return 0;
  else
    return clt->prefac_lensing*wL/(a*chi);
}

static double f_IA_NLA(double a,double chi,ccl_cosmology *cosmo,CCL_ClTracer *clt, int * status)
{
  if(chi<=1E-10)
    return 0;
  else {
    double a=ccl_scale_factor_of_chi(cosmo,chi, status);
    double z=1./a-1;
    double pz=ccl_spline_eval(z,clt->spl_nz);
    double ba=ccl_spline_eval(z,clt->spl_ba);
    double rf=ccl_spline_eval(z,clt->spl_rf);
    double h=cosmo->params.h*ccl_h_over_h0(cosmo,a,status)/CLIGHT_HMPC;

    return pz*ba*rf*h/(chi*chi);
  }
}

//Transfer function for shear
//l -> angular multipole
//k -> wavenumber modulus
//cosmo -> ccl_cosmology object
//w -> CCL_ClWorskpace object
//clt -> CCL_ClTracer object (must be of the ccl_weak_lensing_tracer type)
static double transfer_wl(int l,double k,
			  ccl_cosmology *cosmo,CCL_ClWorkspace *w,CCL_ClTracer *clt, int * status)
{
  double ret=0;
  double chi=(l+0.5)/k;
  if(chi<=clt->chimax) {
    double a=ccl_scale_factor_of_chi(cosmo,chi,status);
    double f_all=f_lensing(a,chi,cosmo,clt,status);
    if(clt->has_intrinsic_alignment)
      f_all+=f_IA_NLA(a,chi,cosmo,clt,status);

    ret=f_all;
  }

  return sqrt((l+2.)*(l+1.)*l*(l-1.))*ret/(k*k);
  //return (l+1.)*l*ret/(k*k);
}

static double transfer_cmblens(int l,double k,ccl_cosmology *cosmo,CCL_ClTracer *clt,int *status)
{
  double chi=(l+0.5)/k;
  if(chi>=clt->chi_source)
    return 0;

  if(chi<=clt->chimax) {
    double a=ccl_scale_factor_of_chi(cosmo,chi,status);
    double w=1-chi/clt->chi_source;
    return clt->prefac_lensing*l*(l+1.)*w/(a*chi*k*k);
  }
  return 0;
}

//Wrapper for transfer function
//l -> angular multipole
//k -> wavenumber modulus
//cosmo -> ccl_cosmology object
//clt -> CCL_ClTracer object
static double transfer_wrap(int il,double lk,ccl_cosmology *cosmo,
			    CCL_ClWorkspace *w,CCL_ClTracer *clt, int * status)
{
  double transfer_out=0;
  double k=pow(10.,lk);

  if(clt->tracer_type==ccl_number_counts_tracer)
    transfer_out=transfer_nc(w->l_arr[il],k,cosmo,w,clt,status);
  else if(clt->tracer_type==ccl_weak_lensing_tracer)
    transfer_out=transfer_wl(w->l_arr[il],k,cosmo,w,clt,status);
  else if(clt->tracer_type==ccl_cmb_lensing_tracer)
    transfer_out=transfer_cmblens(w->l_arr[il],k,cosmo,clt,status);
  else
    transfer_out=-1;
  return transfer_out;
}

//Params for power spectrum integrand
typedef struct {
  int il;
  ccl_cosmology *cosmo;
  CCL_ClWorkspace *w;
  CCL_ClTracer *clt1;
  CCL_ClTracer *clt2;
  int *status;
} IntClPar;

//Integrand for integral power spectrum
static double cl_integrand(double lk,void *params)
{
  double d1,d2;
  IntClPar *p=(IntClPar *)params;
  d1=transfer_wrap(p->il,lk,p->cosmo,p->w,p->clt1,p->status);
  if(d1==0)
    return 0;
  d2=transfer_wrap(p->il,lk,p->cosmo,p->w,p->clt2,p->status);
  if(d2==0)
    return 0;

  double k=pow(10.,lk);
  double chi=(p->w->l_arr[p->il]+0.5)/k;
  double a=ccl_scale_factor_of_chi(p->cosmo,chi,p->status);
  double pk=ccl_nonlin_matter_power(p->cosmo,k,a,p->status);

  return k*pk*d1*d2;
}

//Figure out k intervals where the Limber kernel has support
//clt1 -> tracer #1
//clt2 -> tracer #2
//l    -> angular multipole
//lkmin, lkmax -> log10 of the range of scales where the transfer functions have support
static void get_k_interval(ccl_cosmology *cosmo,CCL_ClWorkspace *w,
			   CCL_ClTracer *clt1,CCL_ClTracer *clt2,int l,
			   double *lkmin,double *lkmax)
{
  double chimin,chimax;
  int cut_low_1=0,cut_low_2=0;

  //Define a minimum distance only if no lensing is needed
  if((clt1->tracer_type==ccl_number_counts_tracer) && (clt1->has_magnification==0)) cut_low_1=1;
  if((clt2->tracer_type==ccl_number_counts_tracer) && (clt2->has_magnification==0)) cut_low_2=1;

  if(cut_low_1) {
    if(cut_low_2) {
      chimin=fmax(clt1->chimin,clt2->chimin);
      chimax=fmin(clt1->chimax,clt2->chimax);
    }
    else {
      chimin=clt1->chimin;
      chimax=clt1->chimax;
    }
  }
  else if(cut_low_2) {
    chimin=clt2->chimin;
    chimax=clt2->chimax;
  }
  else {
    chimin=0.5*(l+0.5)/ccl_splines->K_MAX;
    chimax=2*(l+0.5)/ccl_splines->K_MIN;
  }

  if(chimin<=0)
    chimin=0.5*(l+0.5)/ccl_splines->K_MAX;

  *lkmax=log10(fmin( ccl_splines->K_MAX  ,2  *(l+0.5)/chimin));
  *lkmin=log10(fmax( ccl_splines->K_MIN  ,0.5*(l+0.5)/chimax));
}

//Compute angular power spectrum between two bins
//cosmo -> ccl_cosmology object
//il -> index in angular multipole array
//clt1 -> tracer #1
//clt2 -> tracer #2
static double ccl_angular_cl_native(ccl_cosmology *cosmo,CCL_ClWorkspace *cw,int il,
				    CCL_ClTracer *clt1,CCL_ClTracer *clt2,int * status)
{
  int clastatus=0, gslstatus;
  IntClPar ipar;
  double result=0,eresult;
  double lkmin,lkmax;
  gsl_function F;
  gsl_integration_workspace *w=gsl_integration_workspace_alloc(ccl_gsl->N_ITERATION);

  ipar.il=il;
  ipar.cosmo=cosmo;
  ipar.w=cw;
  ipar.clt1=clt1;
  ipar.clt2=clt2;
  ipar.status = &clastatus;
  F.function=&cl_integrand;
  F.params=&ipar;
  get_k_interval(cosmo,cw,clt1,clt2,cw->l_arr[il],&lkmin,&lkmax);
  // This computes the angular power spectra in the Limber approximation between two quantities a and b:
  //  C_ell^ab = 2/(2*ell+1) * Integral[ Delta^a_ell(k) Delta^b_ell(k) * P(k) , k_min < k < k_max ]
  // Note that we use log10(k) as an integration variable, and the ell-dependent prefactor is included
  // at the end of this function.
  gslstatus=gsl_integration_qag(&F, lkmin, lkmax, 0,
                                ccl_gsl->INTEGRATION_LIMBER_EPSREL, ccl_gsl->N_ITERATION,
                                ccl_gsl->INTEGRATION_LIMBER_GAUSS_KRONROD_POINTS,
                                w, &result, &eresult);
  gsl_integration_workspace_free(w);

  // Test if a round-off error occured in the evaluation of the integral
  // If so, try another integration function, more robust but potentially slower
  if(gslstatus == GSL_EROUND) {
    ccl_raise_gsl_warning(gslstatus, "ccl_cls.c: ccl_angular_cl_native(): Default GSL integration failure, attempting backup method.");
    gsl_integration_cquad_workspace *w_cquad= gsl_integration_cquad_workspace_alloc (ccl_gsl->N_ITERATION);
    size_t nevals=0;
    gslstatus=gsl_integration_cquad(&F, lkmin, lkmax, 0,
				    ccl_gsl->INTEGRATION_LIMBER_EPSREL,
				    w_cquad, &result, &eresult, &nevals);
    gsl_integration_cquad_workspace_free(w_cquad);
  }
  if(gslstatus!=GSL_SUCCESS || *ipar.status) {
    ccl_raise_gsl_warning(gslstatus, "ccl_cls.c: ccl_angular_cl_native():");
    // If an error status was already set, don't overwrite it.
    if(*status == 0){
        *status=CCL_ERROR_INTEG;
        ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_angular_cl_native(): error integrating over k\n");
    }
    return -1;
  }
  ccl_check_status(cosmo,status);

  return result*M_LN10/(cw->l_arr[il]+0.5);
}

void ccl_angular_cls(ccl_cosmology *cosmo,CCL_ClWorkspace *w,
		     CCL_ClTracer *clt1,CCL_ClTracer *clt2,
		     int nl_out,int *l_out,double *cl_out,int *status)
{
  int ii,do_angpow;
  double *l_nodes,*cl_nodes;
  SplPar *spcl_nodes;

  //First check if ell range is within workspace
  for(ii=0;ii<nl_out;ii++) {
    if(l_out[ii]>w->lmax) {
      *status=CCL_ERROR_SPLINE_EV;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_angular_cls(); "
	     "requested l beyond range allowed by workspace\n");
      return;
    }
  }

  if(*status==0) {
    //Allocate array for power spectrum at interpolation nodes
    l_nodes=(double *)malloc(w->n_ls*sizeof(double));
    if(l_nodes==NULL) {
      *status=CCL_ERROR_MEMORY;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_angular_cls(); memory allocation\n");
    }
  }

  if(*status==0) {
    cl_nodes=(double *)malloc(w->n_ls*sizeof(double));
    if(cl_nodes==NULL) {
      *status=CCL_ERROR_MEMORY;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_cl_angular_cls(); memory allocation\n");
    }
  }

  if(*status==0) {
    for(ii=0;ii<w->n_ls;ii++)
      l_nodes[ii]=(double)(w->l_arr[ii]);

    do_angpow=0;
    //Now check if angpow is needed at all
    if(w->l_limber>0) {
      for(ii=0;ii<w->n_ls;ii++) {
	if(w->l_arr[ii]<=w->l_limber)
	  do_angpow=1;
      }
    }
#ifndef HAVE_ANGPOW
    do_angpow=0;
#endif //HAVE_ANGPOW

    //Resort to Limber if we have lensing (this will hopefully only be temporary)
    if(clt1->tracer_type==ccl_weak_lensing_tracer || clt2->tracer_type==ccl_weak_lensing_tracer ||
       clt1->has_magnification || clt2->has_magnification) {
      do_angpow=0;
    }

    //Use angpow if non-limber is needed
    if(do_angpow)
      ccl_angular_cls_angpow(cosmo,w,clt1,clt2,cl_nodes,status);
    ccl_check_status(cosmo,status);
  }

  if(*status==0) {
    //Compute limber nodes
    for(ii=0;ii<w->n_ls;ii++) {
      if((!do_angpow) || (w->l_arr[ii]>w->l_limber))
	cl_nodes[ii]=ccl_angular_cl_native(cosmo,w,ii,clt1,clt2,status);
    }

    //Interpolate into ells requested by user
    spcl_nodes=ccl_spline_init(w->n_ls,l_nodes,cl_nodes,0,0);
    if(spcl_nodes==NULL) {
      *status=CCL_ERROR_MEMORY;
      ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: ccl_cl_angular_cls(); memory allocation\n");
    }
  }

  if(*status==0) {
    for(ii=0;ii<nl_out;ii++)
      cl_out[ii]=ccl_spline_eval((double)(l_out[ii]),spcl_nodes);
  }

  //Cleanup
  ccl_spline_free(spcl_nodes);
  free(cl_nodes);
  free(l_nodes);
}

static int check_clt_fa_inconsistency(CCL_ClTracer *clt,int func_code)
{
  if(((func_code==ccl_trf_nz) && (clt->tracer_type==ccl_cmb_lensing_tracer)) || //lensing has no n(z)
     (((func_code==ccl_trf_bz) || (func_code==ccl_trf_sz) || (func_code==ccl_trf_wM)) &&
      (clt->tracer_type!=ccl_number_counts_tracer)) || //bias and magnification only for clustering
     (((func_code==ccl_trf_rf) || (func_code==ccl_trf_ba) || (func_code==ccl_trf_wL)) &&
      (clt->tracer_type!=ccl_weak_lensing_tracer))) //IAs only for weak lensing
    return 1;
  if((((func_code==ccl_trf_sz) || (func_code==ccl_trf_wM)) &&
      (clt->has_magnification==0)) || //correct combination, but no magnification
     (((func_code==ccl_trf_rf) || (func_code==ccl_trf_ba)) &&
      (clt->has_intrinsic_alignment==0))) //Correct combination, but no IAs
    return 1;
  return 0;
}

double ccl_get_tracer_fa(ccl_cosmology *cosmo,CCL_ClTracer *clt,double a,int func_code,int *status)
{
  SplPar *spl;

  if(check_clt_fa_inconsistency(clt,func_code)) {
    *status=CCL_ERROR_INCONSISTENT;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: inconsistent combination of tracer and internal function to be evaluated");
    return -1;
  }

  switch(func_code) {
  case ccl_trf_nz :
    spl=clt->spl_nz;
    break;
  case ccl_trf_bz :
    spl=clt->spl_bz;
    break;
  case ccl_trf_sz :
    spl=clt->spl_sz;
    break;
  case ccl_trf_rf :
    spl=clt->spl_rf;
    break;
  case ccl_trf_ba :
    spl=clt->spl_ba;
    break;
  case ccl_trf_wL :
    spl=clt->spl_wL;
    break;
  case ccl_trf_wM :
    spl=clt->spl_wM;
    break;
  }

  double x;
  if((func_code==ccl_trf_wL) || (func_code==ccl_trf_wM))
    x=ccl_comoving_radial_distance(cosmo,a,status); //x-variable is comoving distance for lensing kernels
  else
    x=1./a-1; //x-variable is redshift by default

  return ccl_spline_eval(x,spl);
}

int ccl_get_tracer_fas(ccl_cosmology *cosmo,CCL_ClTracer *clt,int na,double *a,double *fa,
		       int func_code,int *status)
{
  SplPar *spl;

  if(check_clt_fa_inconsistency(clt,func_code)) {
    *status=CCL_ERROR_INCONSISTENT;
    ccl_cosmology_set_status_message(cosmo, "ccl_cls.c: inconsistent combination of tracer and internal function to be evaluated");
    return -1;
  }

  switch(func_code) {
  case ccl_trf_nz :
    spl=clt->spl_nz;
    break;
  case ccl_trf_bz :
    spl=clt->spl_bz;
    break;
  case ccl_trf_sz :
    spl=clt->spl_sz;
    break;
  case ccl_trf_rf :
    spl=clt->spl_rf;
    break;
  case ccl_trf_ba :
    spl=clt->spl_ba;
    break;
  case ccl_trf_wL :
    spl=clt->spl_wL;
    break;
  case ccl_trf_wM :
    spl=clt->spl_wM;
    break;
  }

  int compchi = (func_code==ccl_trf_wL) || (func_code==ccl_trf_wM);

  int ia;
  for(ia=0;ia<na;ia++) {
    double x;
    if(compchi) //x-variable is comoving distance for lensing kernels
      x=ccl_comoving_radial_distance(cosmo,a[ia],status);
    else //x-variable is redshift by default
      x=1./a[ia]-1;
    fa[ia]=ccl_spline_eval(x,spl);
  }

  return 0;
}


/////////////////
//example25.5.c
/////////////////
#include <stdio.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_odeiv.h>
#include <math.h>

typedef struct{
  double *a; //y=a[0]x[0]+a[1]x[1]+...a[ka]y[ka]+b[0]u[0]+b[1]u[1]+...+b[kb]u[kb]
  //  double *a; //y[t+1]=a[0]y[t]+a[1]y[t-1]+...a[ka]y[t-ka]+b[0]u[t]+b[1]u[t-1]+...+b[kb]u[t-kb]
  double *b;
  double *x;
  double *u;
  int k,kx,ku;
  double xmax;
  double umax;
  double y;
  int _dim;
  double *_y;
  double *_y_err;
  double *_dydt_in;
  double *_dydt_out;
  double _t;
} CRANE;

#define dim_crane 4
int cranefunc (double t, const double y[], double f[], void *params)
{
  CRANE *c = (CRANE *)params;
  f[0] = y[1];
  f[1] = (-(2*c->dr+c->C)*y[1] -c->ddX*cos(y[0]) -c->g*sin(y[0]))/c->r;
  f[2] = y[3];
  f[3] = (c->F+c->T*sin(y[0]))/c->M;
  return GSL_SUCCESS;
}
////////////////
#define square(x) ((x)*(x))
CRANE crane;
int initialize()
{
  crane.kx=3;
  crane.ku=1;
  crane.k=crane.kx+ku;
  double *a=(double*)malloc(sizeof(double)*ka);
  double *b=&a[kx];
  double *x=(double*)malloc(sizeof(double)*k+1);
  double *u=&x[kx];
  crane.umax=20.0;//check//  crane.Fmax=30;//check

#ifdef CRANESUB
  //  crane.Fmax=_crane_Fmax;//check//  crane.Fmax=30;//check
  //  crane.xmax=20.0;//check//  crane.Fmax=30;//check
  if(_AP_umax>0) AP_u_max=crane.umax=_AP_umax;
  else AP_u_max=crane.umax;

  AP_u_min=-AP_u_max;
  rr=AP_r=_AP_r;//10
  rr_kyoyou=_rr_kyoyou;
  C_MODE=11;
#else
  crane.h=0.01;
#endif
  crane._t=0;
  int i;for(i=0;i<crane._dim;i++) crane._dydt_in[i]=0;

  return(0);
}
#ifndef CRANESUB
char *fn="crane2io.dat";
FILE *fp;
#endif//#ifndef CRANESUB

double plant(double uu)
{
  //store previous x and u
  int i,kx_1=crane.kx-1;
  for(i=0;i<kx_1;i++) crane.x[i+1]=crane.x[i];
  int ku_1=crane.ku-1;
  for(i=0;i<ku_1;i++) crane.u[i+1]=crane.u[i];
  //store former output
  crane.x[0]=crane.y;

  //current input
  if(uu>crane.umax) crane.u[0]=crane.umax;
  else if(uu<-crane.umax) crane.u[0]=-crane.umax;
  // current output
  crane.y=0;
  for(i=0;i<crane.k;i++) crane.y+=crane.a[i]*crane.x[i];
  crane._t++;

#ifndef CRANESUB
  fprintf(fp,"%.7e %.7e %.7e %.7e", crane._t,crane.u[0],crane.x[0],crane.y);//crane.a,crane.da
  fprintf(fp,"\n");
#endif
  return(crane.x);
  //  return(crane.X);
}

#ifndef CRANESUB
int main()
{
  ////////////////////////////////////////////////////
  /// method 1                                     ///
  /// input ddX                                    ///
  /// output a,x,y,F                               ///
  ////////////////////////////////////////////////////
  //  double h = 0.001;//,hh=0.1; h = 0.0001;//,hh=0.1;
  double t0=5;     //stationary
  double t1=5.0+t0;//accelerate(speed up)
  double t2=2.0+t1;//free run
  double t3=5.0+t2;//decelerate(slow down)
  double t4=20+t3;   //free run
  initialize();
  int M=(t4/crane.h)+1;
  double *ddX=(double*)malloc(sizeof(double)*M);
  //  double *F  =(double*)malloc(sizeof(double)*M);
  double Vmax=1;//dX/dt=1[m/s]

  int n;
  double t;
  ////////////////////////////////////////////////////
  ///   set ddX                                    ///
  ////////////////////////////////////////////////////
  int n0 =t0/crane.h+0.5, n4=t4/crane.h+0.5;
  for(t=0;t<t4;t+=crane.h){
    n=t/crane.h;
    if(t<=t0) ddX[n]=0; //zero
    else if(t<=t1) ddX[n]=Vmax/(t1-t0); //accelerate
    else if(t<=t2) ddX[n]=0;            //free move
    else if(t<=t3) ddX[n]=-Vmax/(t3-t2);//decelerate
    else ddX[n]=0;                      //free move
  }
  ////////////////////////////////////////////////////
  ///   Solve by the Runge Kutta Method of GSL     ///
  ////////////////////////////////////////////////////
  fp=fopen(fn,"w");
  fprintf(fp,"#%.7e %d %d %.7e %.7e %.7e %.7e #h,n0,n4,M,n,r\n",crane.h,n0,n4,crane.M,crane.m,crane.r,crane.C);
  //  double dx0=0,dy0=0,da0=0;
  //  for(n=0;n<n4;){
  for(t=0;t<t4;t+=crane.h){
    n=t/crane.h;
    plant(ddX[n]);
  }
  fclose(fp);
  fprintf(stdout,"Results are stored in '%s'.\n",fn);

  gsl_odeiv_step_free (crane._s);
  return 0;
}

#ifndef CRANESUB
int main()
{
  ////////////////////////////////////////////////////
  /// method 1                                     ///
  /// input ddX                                    ///
  /// output a,x,y,F                               ///
  ////////////////////////////////////////////////////
  //  double h = 0.001;//,hh=0.1; h = 0.0001;//,hh=0.1;
  double t0=5;     //stationary
  double t1=5.0+t0;//accelerate(speed up)
  double t2=2.0+t1;//free run
  double t3=5.0+t2;//decelerate(slow down)
  double t4=20+t3;   //free run
  initialize();
  int M=(t4/crane.h)+1;
  double *ddX=(double*)malloc(sizeof(double)*M);
  //  double *F  =(double*)malloc(sizeof(double)*M);
  double Vmax=1;//dX/dt=1[m/s]

  int n;
  double t;
  ////////////////////////////////////////////////////
  ///   set ddX                                    ///
  ////////////////////////////////////////////////////
  int n0 =t0/crane.h+0.5, n4=t4/crane.h+0.5;
  for(t=0;t<t4;t+=crane.h){
    n=t/crane.h;
    if(t<=t0) ddX[n]=0; //zero
    else if(t<=t1) ddX[n]=Vmax/(t1-t0); //accelerate
    else if(t<=t2) ddX[n]=0;            //free move
    else if(t<=t3) ddX[n]=-Vmax/(t3-t2);//decelerate
    else ddX[n]=0;                      //free move
  }
  ////////////////////////////////////////////////////
  ///   Solve by the Runge Kutta Method of GSL     ///
  ////////////////////////////////////////////////////
  fp=fopen(fn,"w");
  fprintf(fp,"#%.7e %d %d %.7e %.7e %.7e %.7e #h,n0,n4,M,n,r\n",crane.h,n0,n4,crane.M,crane.m,crane.r,crane.C);
  //  double dx0=0,dy0=0,da0=0;
  //  for(n=0;n<n4;){
  for(t=0;t<t4;t+=crane.h){
    n=t/crane.h;
    plant(ddX[n]);
  }
  fclose(fp);
  fprintf(stdout,"Results are stored in '%s'.\n",fn);

  gsl_odeiv_step_free (crane._s);
  return 0;
}
#endif //#ifndef SUB


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

/*
 * References:
 *
 * [1] P. C. Hansen, "Discrete Inverse Problems: Insight and Algorithms,"
 * SIAM Press, 2010.
 */

#include <config.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_multifit.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_min.h>

typedef struct
{
  const gsl_vector * S;
  const gsl_vector * UTy;
  double delta0;
  size_t np;
  gsl_vector * workp;
} gcv_params;

static double gcv_func(double lambda, void * params);

/*
gsl_multifit_linear_gcv_init()
  Initialize Generalized Cross Validation parameters

Inputs: y         - right hand side vector
        reg_param - (output) regularization parameters
        UTy       - (output) U^T y
        delta0    - (output) delta0
        work      - workspace
*/

int
gsl_multifit_linear_gcv_init(const gsl_vector * y,
                             gsl_vector * reg_param,
                             gsl_vector * UTy,
                             double * delta0,
                             gsl_multifit_linear_workspace * work)
{
  const size_t n = y->size;

  if (n != work->n)
    {
      GSL_ERROR("y vector does not match workspace", GSL_EBADLEN);
    }
  else if (UTy->size != work->p)
    {
      GSL_ERROR ("UTy vector does not match workspace", GSL_EBADLEN);
    }
  else
    {
      const size_t p = work->p;

      gsl_matrix_view U = gsl_matrix_submatrix(work->A, 0, 0, n, p);
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);

      const double smax = gsl_vector_get(&S.vector, 0);
      const double smin = gsl_vector_get(&S.vector, p - 1);

      double dr; /* residual error from projection */

      double normy = gsl_blas_dnrm2(y);
      double normUTy;

      /* compute projection UTy = U^T y */
      gsl_blas_dgemv (CblasTrans, 1.0, &U.matrix, y, 0.0, UTy);
      normUTy = gsl_blas_dnrm2(UTy);

      /* dr = ||y||^2 - ||U^T y||^2 */
      dr = (normy + normUTy) * (normy - normUTy);

      /* calculate regularization parameters */
      gsl_multifit_linear_lreg(smin, smax, reg_param);

      if (n > p && dr > 0.0)
        *delta0 = dr;
      else
        *delta0 = 0.0;

      return GSL_SUCCESS;
    }
}

/*
gsl_multifit_linear_gcv_curve()
  Calculate Generalized Cross Validation curve for a set
of regularization parameters

Inputs: reg_param - regularization parameters
        UTy       - U^T y vector, size p
        delta0    - delta0
        G         - (output) GCV curve values
        work      - workspace
*/

int
gsl_multifit_linear_gcv_curve(const gsl_vector * reg_param,
                              const gsl_vector * UTy,
                              const double delta0,
                              gsl_vector * G,
                              gsl_multifit_linear_workspace * work)
{
  const size_t n = work->n;
  const size_t p = work->p;
  const size_t N = reg_param->size; /* number of points on GCV curve */

  if (UTy->size != p)
    {
      GSL_ERROR("UTy vector does not match workspace", GSL_EBADLEN);
    }
  else if (G->size != N)
    {
      GSL_ERROR ("size of reg_param and G vectors do not match",
                 GSL_EBADLEN);
    }
  else
    {
      size_t i;

      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
      gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p);

      gcv_params params;

      params.S = &S.vector;
      params.UTy = UTy;
      params.delta0 = delta0;
      params.np = n - p;
      params.workp = &workp.vector;

      for (i = 0; i < N; ++i)
        {
          double lambdai = gsl_vector_get(reg_param, i);
          double Gi = gcv_func(lambdai, &params);

          gsl_vector_set(G, i, Gi);
        }

      return GSL_SUCCESS;
    }
}

/*
gsl_multifit_linear_gcv_min()
  Find regularization parameter which minimizes GCV curve

Inputs: reg_param - regularization parameters
        UTy       - U^T y vector, size p
        G         - GCV curve values
        delta0    - delta0
        lambda    - (output) optimal regularization parameter
        work      - workspace
*/

int
gsl_multifit_linear_gcv_min(const gsl_vector * reg_param,
                            const gsl_vector * UTy,
                            const gsl_vector * G,
                            const double delta0,
                            double * lambda,
                            gsl_multifit_linear_workspace * work)
{
  const size_t n = work->n;
  const size_t p = work->p;
  const size_t npts = reg_param->size; /* number of points on GCV curve */

  if (UTy->size != p)
    {
      GSL_ERROR("UTy vector does not match workspace", GSL_EBADLEN);
    }
  else if (G->size != npts)
    {
      GSL_ERROR ("size of reg_param and G vectors do not match",
                 GSL_EBADLEN);
    }
  else
    {
      int status;
      const size_t max_iter = 500;
      const double tol = 1.0e-4;
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
      gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p);
      gcv_params params;
      int idxG = (int) gsl_vector_min_index(G);
      double a = gsl_vector_get(reg_param, GSL_MIN(idxG + 1, (int) npts - 1));
      double b = gsl_vector_get(reg_param, GSL_MAX(idxG - 1, 0));
      double m = gsl_vector_get(reg_param, idxG);
      size_t iter = 0;
      gsl_function F;

      /* XXX FIXME */
      gsl_min_fminimizer *min_workspace_p;

      if (idxG == 0 || idxG == ((int)npts - 1))
        {
          /* the minimum is an endpoint of the curve, no need to search */
          *lambda = m;
          return GSL_SUCCESS;
        }

      /* XXX FIXME */
      min_workspace_p = gsl_min_fminimizer_alloc(gsl_min_fminimizer_brent);

      params.S = &S.vector;
      params.UTy = UTy;
      params.delta0 = delta0;
      params.np = n - p;
      params.workp = &workp.vector;

      F.function = gcv_func;
      F.params = &params;

      gsl_min_fminimizer_set(min_workspace_p, &F, m, a, b);

      do
        {
          iter++;
          status = gsl_min_fminimizer_iterate(min_workspace_p);

          a = gsl_min_fminimizer_x_lower(min_workspace_p);
          b = gsl_min_fminimizer_x_upper(min_workspace_p);

          status = gsl_min_test_interval(a, b, 0.0, tol);
        }
      while (status == GSL_CONTINUE && iter < max_iter);

      if (status == GSL_SUCCESS)
        *lambda = gsl_min_fminimizer_minimum(min_workspace_p);
      else
        status = GSL_EMAXITER;

      gsl_min_fminimizer_free(min_workspace_p);

      return status;
    }
}

/*
gsl_multifit_linear_gcv_calc()
  Calculate GCV function G(lambda) for given lambda

Inputs: reg_param - regularization parameters
        UTy       - U^T y vector, size p
        delta0    - delta0
        G         - (output) GCV curve values
        work      - workspace
*/

double
gsl_multifit_linear_gcv_calc(const double lambda,
                             const gsl_vector * UTy,
                             const double delta0,
                             gsl_multifit_linear_workspace * work)
{
  const size_t n = work->n;
  const size_t p = work->p;

  if (UTy->size != p)
    {
      GSL_ERROR_VAL("UTy vector does not match workspace", GSL_EBADLEN, 0.0);
    }
  else
    {
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
      gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p);
      gcv_params params;
      double G;

      params.S = &S.vector;
      params.UTy = UTy;
      params.delta0 = delta0;
      params.np = n - p;
      params.workp = &workp.vector;

      G = gcv_func(lambda, &params);

      return G;
    }
}

/*
gsl_multifit_linear_gcv()
  Calculate Generalized Cross Validation curve for a set
of regularization parameters

Inputs: y         - right hand side vector
        reg_param - (output) regularization parameters
        G         - (output) GCV curve values
        lambda    - (output) optimal regularization parameter which
                    minimizes GCV curve
        G_lambda  - (output) G(lambda) value at optimal parameter
        work      - workspace
*/

int
gsl_multifit_linear_gcv(const gsl_vector * y,
                        gsl_vector * reg_param,
                        gsl_vector * G,
                        double * lambda,
                        double * G_lambda,
                        gsl_multifit_linear_workspace * work)
{
  const size_t n = y->size;
  const size_t N = G->size; /* number of points on GCV curve */

  if (n != work->n)
    {
      GSL_ERROR("y vector does not match workspace", GSL_EBADLEN);
    }
  else if (reg_param->size != N)
    {
      GSL_ERROR ("size of reg_param and G vectors do not match",
                 GSL_EBADLEN);
    }
  else
    {
      int status;
      const size_t p = work->p;
      gsl_vector_view UTy = gsl_vector_subvector(work->xt, 0, p);
      double delta0;

      status = gsl_multifit_linear_gcv_init(y, reg_param, &UTy.vector, &delta0, work);
      if (status)
        return status;

      status = gsl_multifit_linear_gcv_curve(reg_param, &UTy.vector, delta0, G, work);
      if (status)
        return status;

      status = gsl_multifit_linear_gcv_min(reg_param, &UTy.vector, G, delta0, lambda, work);
      if (status)
        return status;

      *G_lambda = gsl_multifit_linear_gcv_calc(*lambda, &UTy.vector, delta0, work);

      return GSL_SUCCESS;
    }
}

static double
gcv_func(double lambda, void * params)
{
  gcv_params * par = (gcv_params *) params;
  const gsl_vector *S = par->S;
  const gsl_vector *UTy = par->UTy;
  double delta0 = par->delta0;
  size_t np = par->np;
  gsl_vector *workp = par->workp;
  const size_t p = S->size;
  size_t i;
  double lambda_sq = lambda * lambda;
  double G, d, norm;
  double sumf = 0.0;

  /* compute workp = 1 - filter_factors */
  for (i = 0; i < p; ++i)
    {
      double si = gsl_vector_get(S, i);
      double fi = lambda_sq / (si * si + lambda_sq);
      gsl_vector_set(workp, i, fi);
      sumf += fi;
    }

  d = (double)np + sumf;

  gsl_vector_mul(workp, UTy);
  norm = gsl_blas_dnrm2(workp);

  G = (norm*norm + delta0) / (d * d);

  return G;
}


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

/* Author:  G. Jungman */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_sf_exp.h>
#include <gsl/gsl_sf_result.h>

int
gsl_sf_result_smash_e(const gsl_sf_result_e10 * re, gsl_sf_result * r)
{
  if(re->e10 == 0) {
    /* nothing to smash */
    r->val = re->val;
    r->err = re->err;
    return GSL_SUCCESS;
  }
  else {
    const double av = fabs(re->val);
    const double ae = fabs(re->err);

    if(   GSL_SQRT_DBL_MIN < av && av < GSL_SQRT_DBL_MAX
       && GSL_SQRT_DBL_MIN < ae && ae < GSL_SQRT_DBL_MAX
       && 0.49*GSL_LOG_DBL_MIN  < re->e10 && re->e10 < 0.49*GSL_LOG_DBL_MAX
       ) {
      const double scale = exp(re->e10 * M_LN10);
      r->val = re->val * scale;
      r->err = re->err * scale;
      return GSL_SUCCESS;
    }
    else {
      return gsl_sf_exp_mult_err_e(re->e10*M_LN10, 0.0, re->val, re->err, r);
    }
  }
/*
  int stat_v;
  int stat_e;

  if(re->val == 0.0) {
    r->val = 0.0;
    stat_v = GSL_SUCCESS;
  }
  else {
    gsl_sf_result r_val;
    const double s = GSL_SIGN(re->val);
    const double x_v = re->e10*M_LN10 + log(fabs(re->val));
    stat_v = gsl_sf_exp_e(x_v, &r_val);
    r->val = s * r_val.val;
  }

  if(re->err == 0.0) {
    r->err = 0.0;
    stat_e = GSL_SUCCESS;
  }
  else if(re->val != 0.0) {
    r->err = fabs(r->val * re->err/re->val);
    stat_e = GSL_SUCCESS;
  }
  else {
    gsl_sf_result r_err;
    const double x_e = re->e10*M_LN10 + log(fabs(re->err));
    stat_e = gsl_sf_exp_e(x_e, &r_err);
    r->err = r_err.val;
  }

  return GSL_ERROR_SELECT_2(stat_v, stat_e);
*/
}