/* $Id: fors_utils.c,v 1.32 2013-10-09 15:58:42 cgarcia Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-10-09 15:58:42 $
 * $Revision: 1.32 $
 * $Name: not supported by cvs2svn $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <fors_utils.h>

#include <fors_pfits.h>

#include <math.h>
#include <stdbool.h>

/*----------------------------------------------------------------------------*/
/**
 * @defgroup fors_utils     Miscellaneous Utilities
 */
/*----------------------------------------------------------------------------*/

/**@{*/

/* Required CPL version */
#define REQ_CPL_MAJOR 4
#define REQ_CPL_MINOR 0
#define REQ_CPL_MICRO 0

const double STDEV_PR_MAD = 1/0.6744897;  /* standard deviations per median
                                             absolute deviation,
                                             assuming a normal distribution */

/*----------------------------------------------------------------------------*/
/**
  @brief    Get the pipeline copyright and license
  @return   The copyright and license string

  The function returns a pointer to the statically allocated license string.
  This string should not be modified using the returned pointer.
 */
/*----------------------------------------------------------------------------*/
const char * fors_get_license(void)
{
    const char *const fors_license = 
        "This file is currently part of the FORS Instrument Pipeline\n"
        "Copyright (C) 2002-2011 European Southern Observatory\n"
        "\n"
        "This program is free software; you can redistribute it and/or modify\n"
        "it under the terms of the GNU General Public License as published by\n"
        "the Free Software Foundation; either version 2 of the License, or\n"
        "(at your option) any later version.\n"
        "\n"
        "This program is distributed in the hope that it will be useful,\n"
        "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
        "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
        "GNU General Public License for more details.\n"
        "\n"
        "You should have received a copy of the GNU General Public License\n"
        "along with this program; if not, write to the Free Software Foundation,\n"
        "Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n";
    return fors_license ;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Issue a banner with the pipeline version
 */
/*----------------------------------------------------------------------------*/
void fors_print_banner(void)
{
    cpl_msg_info(__func__, "*****************************************");
    cpl_msg_info(__func__, "Welcome to FORS Pipeline release %s",
                 PACKAGE_VERSION);
    cpl_msg_info(__func__, "*****************************************");
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get FORS library binary version number
   @return   Binary version number
*/
/*----------------------------------------------------------------------------*/
int
fors_get_version_binary(void)
{
    /*
      This is a good opportunity to verify that the compile time
      and runtime CPL versions are recent enough, and consistent 
    */

#ifdef CPL_VERSION_CODE
#if CPL_VERSION_CODE >= CPL_VERSION(REQ_CPL_MAJOR, REQ_CPL_MINOR, REQ_CPL_MICRO)
    cpl_msg_debug(cpl_func, 
                  "Compile time CPL version code was %d. "
                  "Required is version %d.%d.%d, code %d",
                  CPL_VERSION_CODE, REQ_CPL_MAJOR, REQ_CPL_MINOR, REQ_CPL_MICRO,
                  CPL_VERSION(REQ_CPL_MAJOR, REQ_CPL_MINOR, REQ_CPL_MICRO));
#else
#error CPL version too old
#endif
#else  /* ifdef CPL_VERSION_CODE */
#error CPL_VERSION_CODE not defined. CPL version too old
#endif

    if (cpl_version_get_major() < REQ_CPL_MAJOR ||
        (cpl_version_get_major() == REQ_CPL_MAJOR && 
         (int) cpl_version_get_minor() < REQ_CPL_MINOR) ||
        
        (cpl_version_get_major() == REQ_CPL_MAJOR &&
         cpl_version_get_minor() == REQ_CPL_MINOR && 
         (int) cpl_version_get_micro() < REQ_CPL_MICRO)
        ) {
        /* cast suppresses warning about comparing unsigned with 0 */

        cpl_msg_warning(cpl_func, 
                        "Runtime CPL version %s (%d.%d.%d) "
                        "is not supported. "
                        "Please update to CPL version %d.%d.%d or later", 
                        cpl_version_get_version(),
                        cpl_version_get_major(),
                        cpl_version_get_minor(),
                        cpl_version_get_micro(),
                        REQ_CPL_MAJOR,
                        REQ_CPL_MINOR,
                        REQ_CPL_MICRO);
    }
    else {
        cpl_msg_debug(cpl_func,
                      "Runtime CPL version %s (%d.%d.%d) detected, "
                      "%d.%d.%d or later required",
                      cpl_version_get_version(),
                      cpl_version_get_major(),
                      cpl_version_get_minor(),
                      cpl_version_get_micro(),
                      REQ_CPL_MAJOR,
                      REQ_CPL_MINOR,
                      REQ_CPL_MICRO);
    }

    /* Fixme: Test that compile and runtime versions compare equal.
       This requires CPL to provide the major/minor/micro version numbers
       as preprocessor symbols, not just a code. */
    
    /* As defined in config.h */
    return FORS_BINARY_VERSION;
}


/*----------------------------------------------------------------------------*/
/**
   @brief  Pseudo-random gaussian distributed number
   @return Pseudo-random gasssian value with mean zero, stdev 1, based
   on C's rand()

   It is left to the user when/how to call srand().
*/
/*----------------------------------------------------------------------------*/
double fors_rand_gauss(void)
{
    static double V1, V2, S;
    static int phase = 0;
    double X;
    
    if(phase == 0) {
    do {
        double U1 = (double)rand() / RAND_MAX;
        double U2 = (double)rand() / RAND_MAX;
        
        V1 = 2 * U1 - 1;
        V2 = 2 * U2 - 1;
        S = V1 * V1 + V2 * V2;
    } while(S >= 1 || S == 0);
    
    X = V1 * sqrt(-2 * log(S) / S);
    } else
    X = V2 * sqrt(-2 * log(S) / S);
    
    phase = 1 - phase;
    
    return X;
}

/*----------------------------------------------------------------------------*/
/** 
 * @brief Same as cpl_tools_get_kth_double
 */
/*----------------------------------------------------------------------------*/
double fors_tools_get_kth_double(
    double  *   a,
    int         n,
    int         k)
{
    double x ;
    int    i, j, l, m ;

    cpl_ensure(a, CPL_ERROR_NULL_INPUT, 0.00) ;

    l=0 ; m=n-1 ;
    while (l<m) {
        x=a[k] ;
        i=l ;
        j=m ;
        do {
            while (a[i]<x) i++ ;
            while (x<a[j]) j-- ;
            if (i<=j) {
                //CPL_DOUBLE_SWAP(a[i],a[j]) ;
                double temp = a[i];
                a[i] = a[j];
                a[j] = temp;
                i++ ; j-- ;
            }
        } while (i<=j) ;
        if (j<k) l=i ;
        if (k<i) m=j ;
    }
    return a[k] ;
}

#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/** 
 * @brief Unbiased median
 */
/*----------------------------------------------------------------------------*/
float fors_tools_get_median_float(float *a, int n)
{
    return (n % 2 == 0) ?
        (fors_tools_get_kth_float(a, n, (n-1)/2) +
         fors_tools_get_kth_float(a, n, (n/2))) / 2.0
        : fors_tools_get_kth_float(a, n, n/2);
}

#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/**
 * @brief Biased median
 */
/*----------------------------------------------------------------------------*/
float fors_tools_get_median_fast_float(float *a, int n)
{
    return fors_tools_get_kth_float(a, n, n/2);
}

#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/** 
 * @brief Same as cpl_tools_get_kth_float
 */
/*----------------------------------------------------------------------------*/
float fors_tools_get_kth_float(
    float  *   a,
    int         n,
    int         k)
{
    float x ;
    int    i, j, l, m ;

    cpl_ensure(a, CPL_ERROR_NULL_INPUT, 0.00) ;

    l=0 ; m=n-1 ;
    while (l<m) {
        x=a[k] ;
        i=l ;
        j=m ;
        do {
            while (a[i]<x) i++ ;
            while (x<a[j]) j-- ;
            if (i<=j) {
                //CPL_FLOAT_SWAP(a[i],a[j]) ;
                float temp = a[i];
                a[i] = a[j];
                a[j] = temp;
                i++ ; j-- ;
            }
        } while (i<=j) ;
        if (j<k) l=i ;
        if (k<i) m=j ;
    }
    return a[k] ;
}

#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/** 
 * @brief Get frame type as a string
 * @param  f      frame
 * @return frame type, or NULL
 */
/*----------------------------------------------------------------------------*/
const char *
fors_frame_get_type_string(const cpl_frame *f)
{
    assure( f != NULL, return NULL, "Null frame" );

    switch (cpl_frame_get_type(f)) {
    case CPL_FRAME_TYPE_NONE:   return "NONE";      break;
    case CPL_FRAME_TYPE_IMAGE:  return "IMAGE";     break;
    case CPL_FRAME_TYPE_MATRIX: return "MATRIX";    break;
    case CPL_FRAME_TYPE_TABLE:  return "TABLE";     break;
    default: 
        return "unrecognized frame type";      
        break;
    }
}

#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/** 
 * @brief Get frame group as a string
 * @param  f       frame
 * @return frame group, or NULL
 */
/*----------------------------------------------------------------------------*/
const char *
fors_frame_get_group_string(const cpl_frame *f)
{
    assure( f != NULL, return NULL, "Null frame" );
    
    switch (cpl_frame_get_group(f)) {
    case CPL_FRAME_GROUP_NONE:    return "NONE";                     break;
    case CPL_FRAME_GROUP_RAW:     return CPL_FRAME_GROUP_RAW_ID;     break;
    case CPL_FRAME_GROUP_CALIB:   return CPL_FRAME_GROUP_CALIB_ID;   break;
    case CPL_FRAME_GROUP_PRODUCT: return CPL_FRAME_GROUP_PRODUCT_ID; break;
    default:
        return "unrecognized frame group";
        break;
    }
}

#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/** 
 * @brief Get frame level as a string
 * @param  f        frame
 * @return frame level, or NULL
 */
/*----------------------------------------------------------------------------*/
const char *
fors_frame_get_level_string(const cpl_frame *f)
{
    assure( f != NULL, return NULL, "Null frame" );
    
    switch (cpl_frame_get_level(f)) {
    case CPL_FRAME_LEVEL_NONE:        return "NONE";         break;
    case CPL_FRAME_LEVEL_TEMPORARY:   return "TEMPORARY";    break;
    case CPL_FRAME_LEVEL_INTERMEDIATE:return "INTERMEDIATE"; break;
    case CPL_FRAME_LEVEL_FINAL:       return "FINAL";        break;
    default: 
        return "unrecognized frame level";
        break;
    }
}

/*----------------------------------------------------------------------------*/
/** 
 * @brief Print a frame set
 * @param frames Frame set to print
 *
 * This function prints all frames in a CPL frame set.
 */
/*----------------------------------------------------------------------------*/
void
fors_frameset_print(const cpl_frameset *frames)
{
    /* Two special cases: a NULL frame set and an empty frame set */

    if (frames == NULL) {
        cpl_msg_info(cpl_func, "NULL");
    }
    else {
        const cpl_frame *f = NULL;
        f = cpl_frameset_get_first_const(frames);
        
        if (f == NULL) {
            cpl_msg_info(cpl_func, "[Empty frame set]");
        }
        else {
            while(f != NULL) {
                fors_frame_print(f);
                f = cpl_frameset_get_next_const(frames);
            }
        }
    }
    
    return;
}

/*----------------------------------------------------------------------------*/
/** 
 * @brief Print a frame
 * @param f     Frame to print
 *
 * This function prints a CPL frame.
 */
/*----------------------------------------------------------------------------*/
void
fors_frame_print(const cpl_frame *f)
{
    if (f == NULL) {
        cpl_msg_info(cpl_func, "NULL");
    }
    else {
        const char *filename = cpl_frame_get_filename(f);
        const char *tag = cpl_frame_get_tag(f);
        
        if (filename == NULL) {
            filename = "NULL";
        }
        if (tag == NULL) {
            tag = "NULL";
        }
        
        cpl_msg_info(cpl_func, "%-7s %-20s %s", 
                     fors_frame_get_group_string(f),
                     tag,
                     filename);
        
        cpl_msg_debug(cpl_func, "type \t= %s",  fors_frame_get_type_string(f));
        cpl_msg_debug(cpl_func, "group \t= %s", fors_frame_get_group_string(f));
        cpl_msg_debug(cpl_func, "level \t= %s", fors_frame_get_level_string(f));
    }
    
    return;
}


#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
/** 
 * @brief  Extract frames with given tag from frameset
 * @param  frames      frame set
 * @param  tag         to search for
 * @return newly allocated, possibly empty, frameset, or NULL on error
 */
/*----------------------------------------------------------------------------*/
cpl_frameset *
fors_frameset_extract(const cpl_frameset *frames,
                      const char *tag)
{
    cpl_frameset *subset = NULL;
    const cpl_frame *f;

    assure( frames != NULL, return NULL, "Null frameset" );
    assure( tag    != NULL, return NULL, "Null tag" );
    
    subset = cpl_frameset_new();

    for (f = cpl_frameset_find_const(frames, tag);
         f != NULL;
         f = cpl_frameset_find_const(frames, NULL)) {

        cpl_frameset_insert(subset, cpl_frame_duplicate(f));
    }

    return subset;
}

/*----------------------------------------------------------------------------*/
/** 
 * @brief Textual representation of CPL type
 * @param  t         type
 * @return the given type as a string
 */
/*----------------------------------------------------------------------------*/
const char *fors_type_get_string(cpl_type t)
{
    /* Note that CPL_TYPE_STRING is shorthand
       for CPL_TYPE_CHAR | CPL_TYPE_FLAG_ARRAY . */
    
    if (!(t & CPL_TYPE_FLAG_ARRAY))
        switch(t & (~CPL_TYPE_FLAG_ARRAY)) {
        case CPL_TYPE_CHAR:       return "char";    break;
        case CPL_TYPE_UCHAR:      return "uchar";   break;
        case CPL_TYPE_BOOL:       return "boolean"; break;
        case CPL_TYPE_INT:        return "int";     break;
        case CPL_TYPE_UINT:       return "uint";    break;
        case CPL_TYPE_LONG:       return "long";    break;
        case CPL_TYPE_ULONG:      return "ulong";   break;
        case CPL_TYPE_FLOAT:      return "float";   break;
        case CPL_TYPE_DOUBLE:     return "double";  break;
        case CPL_TYPE_POINTER:    return "pointer"; break;
        case CPL_TYPE_INVALID:    return "invalid"; break;
        default:
            return "unrecognized type";
        }
    else
        switch(t & (~CPL_TYPE_FLAG_ARRAY)) {
        case CPL_TYPE_CHAR:       return "string (char array)"; break;
        case CPL_TYPE_UCHAR:      return "uchar array";         break;
        case CPL_TYPE_BOOL:       return "boolean array";       break;
        case CPL_TYPE_INT:        return "int array";           break;
        case CPL_TYPE_UINT:       return "uint array";          break;
        case CPL_TYPE_LONG:       return "long array";          break;
        case CPL_TYPE_ULONG:      return "ulong array";         break;
        case CPL_TYPE_FLOAT:      return "float array";         break;
        case CPL_TYPE_DOUBLE:     return "double array";        break;
        case CPL_TYPE_POINTER:    return "pointer array";       break;
        case CPL_TYPE_INVALID:    return "invalid (array)";     break;
        default:
            return "unrecognized type";
        }
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Set unset parameters to default value
  @param    parlist              A parameter list

  The function initializes the provided parameter list by setting the current
  parameter values to the default parameter values.
 */
/*----------------------------------------------------------------------------*/
void
fors_parameterlist_set_defaults(cpl_parameterlist *parlist)
{
    cpl_parameter *p = NULL;
    bool parameter_is_set;

    p = cpl_parameterlist_get_first(parlist);
    while (p != NULL) {

        /*   EsoRex bug: (well, it's an undocumented-feature 
             that EsoRex, unlike Gasgano, reads the default flag):
        */
        parameter_is_set = cpl_parameter_get_default_flag(p);
        
        if (!parameter_is_set) {
            cpl_type ptype = cpl_parameter_get_type(p);
            switch (ptype) {
            case CPL_TYPE_BOOL:
                cpl_parameter_set_bool(p, cpl_parameter_get_default_bool(p));
                break;
            case CPL_TYPE_INT:
                cpl_parameter_set_int(p, cpl_parameter_get_default_int(p));
                break;
            case CPL_TYPE_DOUBLE:
                cpl_parameter_set_double(p, cpl_parameter_get_default_double(p));
                break;
            case CPL_TYPE_STRING:
                cpl_parameter_set_string(p, cpl_parameter_get_default_string(p));
                break;
            default:
                assure( false, return, "Unknown type of parameter '%s'", 
                        cpl_parameter_get_name(p));
            }
        }
        p = cpl_parameterlist_get_next(parlist);
    }
    
    return;
}

#ifdef CPL_IS_NOT_CRAP
#else
/**
 * @brief    Workaround for cpl_imagelist_collapse_create
 * @param    images         see cpl_imagelist_collapse_create()
 * @return   see cpl_imagelist_collapse_create()
 *
 * This function is the same as cpl_imagelist_collapse_create but
 * does not allocate a new bad pixel buffer when it is not needed.
 *
 * Images with bpm causes the function cpl_image_get_median_window() to
 * slowdown by a factor ~200000 for a 2 megapixel image and 9 pixel window
 */
cpl_image *fors_imagelist_collapse_create(const cpl_imagelist *ilist)
{
    cpl_image *result = cpl_imagelist_collapse_create(ilist);
    
    if (result != NULL && cpl_image_count_rejected(result) == 0) {
        cpl_image_accept_all(result);
    }
    
    return result;
}


/**
 * @brief    Workaround for cpl_imagelist_collapse_median_create
 * @param    images         see fors_imagelist_collapse_create
 * @return   see fors_imagelist_collapse_create
 *
 * See fors_imagelist_collapse_create
 */
cpl_image *fors_imagelist_collapse_median_create(const cpl_imagelist *ilist)
{
    cpl_image *result = cpl_imagelist_collapse_median_create(ilist);
    
    if (result != NULL && cpl_image_count_rejected(result) == 0) {
        cpl_image_accept_all(result);
    }
    
    return result;
}
#endif

#undef cleanup
#define cleanup
/**
 * @brief    Difference between angles
 * @param    a1      1st angle (radians)
 * @param    a2      2nd angle (radians)
 * @return   difference in [0;pi]
 */
double fors_angle_diff(const double *a1, const double *a2)
{
    assure( a1 != NULL, return -1, NULL );
    assure( a2 != NULL, return -1, NULL );

    double d = *a1 - *a2;

    while (d < -M_PI) d += 2*M_PI;
    while (d >  M_PI) d -= 2*M_PI;
    
    return fabs(d);
}

#define MAX_MESSAGE_LENGTH 1024
#undef cleanup
#define cleanup
/**
 * @brief    Print message
 * @param    level    message level
 * @param    fct      function id
 * @param    format   printf style format string
 *
 * This function can be called directly or through the macro fors_msg()
 */
void fors_msg_macro(cpl_msg_severity level, const char *fct, const char *format, ...)
{
    char message[MAX_MESSAGE_LENGTH];
    va_list al;
    
    va_start(al, format);
    vsnprintf(message, MAX_MESSAGE_LENGTH - 1, format, al);
    va_end(al);
    
    message[MAX_MESSAGE_LENGTH - 1] = '\0';

    switch(level) {
    case CPL_MSG_DEBUG:   cpl_msg_debug  (fct, "%s", message); break;
    case CPL_MSG_INFO:    cpl_msg_info   (fct, "%s", message); break;
    case CPL_MSG_WARNING: cpl_msg_warning(fct, "%s", message); break;
    case CPL_MSG_ERROR:   cpl_msg_error  (fct, "%s", message); break;
    default: 
        cpl_msg_error(fct, "Unknown message level: %d", level);
        cpl_msg_error(fct, "%s", message);
        break;
    }
    return;
}


#undef cleanup
#define cleanup
/**
 * @brief    median stacking correction factor
 * @param    n        number of stacked frames
 * @return  correction factor defined as the ratio sigma_median / sigma_avg
 *
 * sigma_median is the propagated error when computing the median of n numbers with
 * the same error.
 * sigma_avg is the propagated error of the average of n numbers with the same error.
 */
double fors_utils_median_corr(int n)
{
    assure( n > 0, return -1, "Illegal number: %d", n);

    const double c[] = {
	/* Format:  n, corr(n), error
	   Numbers computed by numerical experiment 
	   (i.e. generate n normal distributed numbers, 
	   get the median (if n is even, mean of middle elements),
	   measure the median error and median's error error)
	*/
	1, 0.999892, 0.000405,
	2, 0.999868, 0.000413,
	3, 1.159685, 0.000467,
	4, 1.091738, 0.000431,
	5, 1.197186, 0.000473,
	6, 1.134358, 0.000454,
	7, 1.213364, 0.000481,
	8, 1.159116, 0.000466,
	9, 1.223264, 0.000487,
	10, 1.176252, 0.000468,
	11, 1.228136, 0.000491,
	12, 1.187431, 0.000476,
	13, 1.231643, 0.000498,
	14, 1.195670, 0.000488,
	15, 1.235724, 0.000491,
	16, 1.201223, 0.000482,
	17, 1.237393, 0.000487,
	18, 1.207451, 0.000487,
	19, 1.239745, 0.000502,
	20, 1.212639, 0.000490,
	21, 1.241837, 0.000498,
	22, 1.216330, 0.000492,
	23, 1.244426, 0.000508,
	24, 1.221290, 0.000493,
	25, 1.246456, 0.000507,
	26, 1.224591, 0.000498,
	27, 1.248254, 0.000500,
	28, 1.227300, 0.000498,
	29, 1.248467, 0.000503,
	30, 1.229101, 0.000485,
	31, 1.248270, 0.000498,
	32, 1.231945, 0.000493,
	33, 1.249087, 0.000509,
	34, 1.231960, 0.000486,
	35, 1.249525, 0.000500,
	36, 1.231679, 0.000496,
	37, 1.249156, 0.000510,
	38, 1.233630, 0.000494,
	39, 1.249173, 0.000483,
	40, 1.233669, 0.000492,
	41, 1.248756, 0.000510,
	42, 1.235170, 0.000493,
	43, 1.248498, 0.000497,
	44, 1.235864, 0.000501,
	45, 1.248986, 0.000487,
	46, 1.236148, 0.000495,
	47, 1.248720, 0.000507,
	48, 1.236461, 0.000499,
	49, 1.248677, 0.000500,
	50, 1.236832, 0.000499,
	51, 1.249143, 0.000510,
	52, 1.237251, 0.000497,
	53, 1.248619, 0.000510,
	54, 1.237826, 0.000490,
	55, 1.249292, 0.000499,
	56, 1.238721, 0.000492,
	57, 1.248719, 0.000502,
	58, 1.238830, 0.000482,
	59, 1.248869, 0.000491,
	60, 1.239892, 0.000501,
	61, 1.248980, 0.000505,
	62, 1.239435, 0.000506,
	63, 1.249534, 0.000506,
	64, 1.240748, 0.000507,
	65, 1.249158, 0.000501,
	66, 1.240053, 0.000503,
	67, 1.248843, 0.000500,
	68, 1.241417, 0.000499,
	69, 1.249386, 0.000506,
	70, 1.241106, 0.000499,
	71, 1.249540, 0.000509,
	72, 1.240998, 0.000491,
	73, 1.250202, 0.000502,
	74, 1.241989, 0.000491,
	75, 1.249485, 0.000497,
	76, 1.242218, 0.000503,
	77, 1.249733, 0.000506,
	78, 1.240815, 0.000517,
	79, 1.250652, 0.000494,
	80, 1.241356, 0.000501,
	81, 1.250115, 0.000511,
	82, 1.241610, 0.000506,
	83, 1.249751, 0.000504,
	84, 1.242905, 0.000485,
	85, 1.249906, 0.000512,
	86, 1.243211, 0.000502,
	87, 1.250671, 0.000503,
	88, 1.242750, 0.000489,
	89, 1.249779, 0.000502,
	90, 1.243191, 0.000507,
	91, 1.250325, 0.000494,
	92, 1.243411, 0.000493,
	93, 1.250774, 0.000508,
	94, 1.244007, 0.000492,
	95, 1.249777, 0.000503,
	96, 1.243910, 0.000507,
	97, 1.250147, 0.000503,
	98, 1.243634, 0.000501,
	99, 1.250931, 0.000504,
	100, 1.243948, 0.000504};

    if (n <= 100) {
	return c[(n-1)*3 + 1];
    }
    else {
        return sqrt(M_PI/2);
    }
}

/**@}*/
