/* $Id: fors_photometry_impl.c,v 1.86 2013-09-10 19:17:31 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-09-10 19:17:31 $
 * $Revision: 1.86 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_photometry_impl.h>

#include <fors_data.h>
#include <fors_qc.h>
#include <fors_dfs.h>
#include <fors_tools.h>
#include <fors_pfits.h>
#include <fors_polynomial.h>
#include <fors_utils.h>
#include <fors_double.h>
#include <fors_extract.h>

#include <cpl.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

/**
 * @addtogroup fors_photometry
 */

/**@{*/

const char *const fors_photometry_name = "fors_photometry";
const char *const fors_photometry_description_short = "Compute corrected flatfield";
const char *const fors_photometry_author = "Jonas M. Larsen";
const char *const fors_photometry_email = PACKAGE_BUGREPORT;
const char *const fors_photometry_description = 
"Input files:\n"
"  DO category:               Type:       Explanation:             Number:\n"
"  PHOT_TABLE                 FITS table  Expected extinction params  1\n"
"  ALIGNED_PHOT               FITS table  Photometry                  1+\n"
"  MASTER_SKY_FLAT_IMG        FITS image  Master flat field           1\n"
"\n"
"Output files:\n"
"  DO category:               Data type:  Explanation:\n"
"  PHOT_COEFF_TABLE           FITS image  Observed extinction coefficients\n"
"  CORRECTION_MAP             FITS image  Correction map (magnitude)\n"
"  CORRECTION_FACTOR          FITS image  Correction map (flux)\n"
"  MASTER_FLAT_IMG            FITS image  Corrected master flat field\n";

typedef enum fors_fit_ncoeff {
    FORS_FIT_NCOEFF_NO = 0,
    FORS_FIT_NCOEFF_ONE,
    FORS_FIT_NCOEFF_PERFRAME,
    FORS_FIT_NCOEFF_PERNIGHT
} fors_fit_ncoeff;

const struct fors_fit_ncoeff_paropts {
    const char  *no,
                *one,
                *perframe,
                *pernight;
} fors_fit_ncoeff_paropts = {       "no",
                                    "one",
                                    "perframe",
                                    "pernight"};

typedef struct entry
{
    int         frame_index,   /* Counting from zero */
                star_index,    /* Star identification number, count from 0 */
                atm_ext_index;  /* atmospheric extinction index, count from 0 */
    int         atm_ext_identifier;
    double      airmass,
                gain,
                exptime;
    fors_star   *star;
} entry;

/* Declare and define entry_list */
#undef LIST_ELEM
#define LIST_ELEM entry 
#undef LIST_DEFINE
#include <list.h>
#define LIST_DEFINE
#include <list.h>

const double arcsec_tol = 5.0;

entry *entry_new(                           int     frame_index,
                                            int     star_index,
                                            double  airmass,
                                            double  gain,
                                            double  exptime,
                                            int     atm_ext_identifier,
                                            fors_star *star);

void
entry_delete(                               entry **e);

static void
entry_delete_but_standard(                  entry **e);

static double
entry_get_powers_x_y(                       const entry *e,
                                            const cpl_array *powers);

void
entry_list_print(                           const entry_list *l,
                                            cpl_msg_severity level);

static double
entry_get_powers_airmass_color(             const entry *e,
                                            const cpl_array *powers);

static entry_list *
fors_photometry_read_input(                 const cpl_frameset  *alphot_frames,
                                            const fors_setting  *setting,
                                            int (*get_atm_ext_id_function)(
                                                const cpl_propertylist *header),
                                            bool                import_unknown,
                                            int                 *n_frames,
                                            fors_std_star_list  **std_star_list,
                                            int                 filter
                                            );

static fors_std_star*
fors_photometry_read_input_listinsert_star_if_new(
                                            fors_std_star_list  *std_list,
                                            fors_std_star       *std,
                                            double              mind_arcsec);

static void
fors_delete_star_lists(                     entry_list          **el,
                                            fors_std_star_list  **sl);

static bool
fors_fits_compare_string(                   const char  *s1,
                                            const char  *s2);

static void
fors_matrix_null(                           cpl_matrix  **m);

static void
fors_matrix_append_delete(                  cpl_matrix  **m1,
                                            cpl_matrix  **m2);

static double
fors_property_get_num(                      const cpl_property  *prop);

static double
fors_photometry_parameter_get_num(          const cpl_parameterlist *parameters,
                                            const char              *name,
                                            cpl_type                type);

static const char*
fors_photometry_parameter_get_string(       const cpl_parameterlist *parameters,
                                            const char              *name);

static fors_fit_ncoeff
fors_photometry_parameter_get_ncoeff(       const cpl_parameterlist *parameters,
                                            const char              *name);

void fors_photometry_define_parameters(     cpl_parameterlist   *parameters);

static cpl_polynomial*
fors_photometry_define_polyf(               int                 degreef1,
                                            int                 degreef2);

static cpl_polynomial*
fors_photometry_define_polyp(               int                 degreep);

static cpl_error_code
fors_photometry_poly_new_from_coefficients( const cpl_polynomial    *p_def,
                                            const cpl_matrix        *coeffs,
                                            const cpl_matrix        *cov_coeffs,
                                            cpl_polynomial          **poly,
                                            cpl_polynomial          **var_poly);

int
fors_photometry_get_timezone_observer(      const cpl_propertylist  *header);

int
fors_photometry_get_night_id(               const cpl_propertylist *header);

static int
fors_photometry_atm_ext_create_index_by_identifier(
                                            entry_list          *obs_list);

static int
fors_photometry_atm_ext_create_indices(     entry_list      *obsl,
                                            fors_fit_ncoeff fit_e);

//static cpl_error_code
static cpl_table *
fors_photometry_atm_ext_print_index_by_framename(
                                            const entry_list    *obs_list,
                                            const cpl_frameset  *frames);

static cpl_error_code
fors_photometry_check_input_value(          double      value,
                                            double      value_error,
                                            const char  *value_name,
                                            const char  *input_name,
                                            double      min_limit,
                                            double      max_limit,
                                            double      max_error);

static cpl_error_code
fors_photometry_check_fitparam_atm_ext(     entry_list      *obsl,
                                            fors_fit_ncoeff fit_e,
                                            bool            fit_z);

static cpl_error_code
fors_photometry_adjust_fit_mag_flags(       fors_std_star_list  *stdl,
                                            entry_list          *obsl,
                                            bool                override_fit_m,
                                            int                 *n_mag_fits);

static cpl_error_code
fors_photometry_remove_unnecessary(         fors_std_star_list  *std_list,
                                            entry_list          *obs_list,
                                            int                 *n_mag_fits);

static cpl_array*
fors_photometry_count_observations(         fors_std_star_list  *std_list,
                                            entry_list          *obs_list);

static cpl_matrix*
build_equations_lhs_matrix_from_parameters( const entry_list    *obs_list,
                                            const fors_std_star_list  *std_list,
                                            bool                fit_z,
                                            bool                fit_c,
                                            int                 *n_fit_e_cols);

static cpl_matrix*
build_equations_lhs_matrix_from_poly(       const entry_list        *obs_list,
                                            const cpl_polynomial    *poly,
                                            const char              *pname,
                                            double (*powerfunc)(
                                                            const entry*,
                                                            const cpl_array*));

static cpl_error_code
build_equations_rhs_cov(                    const entry_list    *obs_list,
                                            const fors_std_star_list  *std_list,
                                            bool                fit_z,
                                            bool                fit_c,
                                            bool                fit_e,
                                            double              color_coeff,
                                            double              dcolor_coeff,
                                            double              ext_coeff,
                                            double              dext_coeff,
                                            double              zpoint,
                                            double              dzpoint,
                                            cpl_matrix          **rhs,
                                            cpl_matrix          **rhs_cov);

/*----------------------------------------------------------------------------*/
/**
 * @brief    entry constructor
 * @param    star              ownership is transferred, so
 *                             deallocate only using entry_delete
 */
/*----------------------------------------------------------------------------*/
entry *entry_new(                           int     frame_index,
                                            int     star_index,
                                            double  airmass,
                                            double  gain,
                                            double  exptime,
                                            int     atm_ext_identifier,
                                            fors_star *star)
{
    entry *e = cpl_malloc(sizeof(*e));

    e->frame_index = frame_index;
    e->star_index = star_index;
    e->atm_ext_index = -1;  /* means undefined */
    e->airmass = airmass;
    e->gain = gain;
    e->exptime = exptime;
    
    e->atm_ext_identifier = atm_ext_identifier;

    e->star = star;
    
    return e;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief  Destructor
 * @param  e      to delete
 */
/*----------------------------------------------------------------------------*/
void
entry_delete(                               entry **e)
{
    if (e && *e) {
        fors_star_delete(&(*e)->star);
        cpl_free(*e); *e = NULL;
    }
    return;
}

/*----------------------------------------------------------------------------*/
/**
 * @brief  Destructor
 * @param  e      to delete
 */
/*----------------------------------------------------------------------------*/
static void
entry_delete_but_standard(                  entry **e)
{
    if (e && *e) {
        fors_star_delete_but_standard(&(*e)->star);
        cpl_free(*e); *e = NULL;
    }
    return;
}


/*----------------------------------------------------------------------------*/
/**
 * @brief  Print list
 * @param  l         to print
 * @param  level     message level
 */
/*----------------------------------------------------------------------------*/
void
entry_list_print(                           const entry_list *l,
                                            cpl_msg_severity level)
{
    const entry *e;
    
    fors_msg(level, "Observation list:");
    cpl_msg_indent_more();
    for (e = entry_list_first_const(l);
         e != NULL;
         e = entry_list_next_const(l)) {

        fors_msg(level,
                 "frame %d, star %d: airmass = %f, gain = %f, exptime = %f s",
                 e->frame_index, e->star_index,
                 e->airmass, e->gain, e->exptime);

        fors_star_print(level, e->star);
    }
    cpl_msg_indent_less();

    return;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
static double
entry_get_powers_x_y(                       const entry     *e,
                                            const cpl_array *powers)
{
    passure(powers != NULL && e != NULL, return sqrt(-1));  /* return NaN */
    passure(cpl_array_get_size(powers) == 2, return sqrt(-1));
    return  pow(e->star->pixel->x,      cpl_array_get(powers, 0, NULL))
            * pow(e->star->pixel->y,    cpl_array_get(powers, 1, NULL));
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/*----------------------------------------------------------------------------*/
static double
entry_get_powers_airmass_color(             const entry *e,
                                            const cpl_array *powers)
{
    passure(powers != NULL && e != NULL, return sqrt(-1));  /* return NaN */
    passure(cpl_array_get_size(powers) == 2, return sqrt(-1));
    return  pow(e->airmass,             cpl_array_get(powers, 0, NULL))
            * pow(e->star->id->color,   cpl_array_get(powers, 1, NULL));
}

/*----------------------------------------------------------------------------*/
/**
 * @brief   Delete a fors_std_star_list and an entry list referring to it.
 * @param   el  Entry list
 * @param   sl  Standard star list
 * @return  Nothing
 */
/*----------------------------------------------------------------------------*/
static void
fors_delete_star_lists(                     entry_list          **el,
                                            fors_std_star_list  **sl)
{
    entry *e;
    if (el != NULL && *el != NULL)
    {
        for (   e = entry_list_first(*el);
                e != NULL;
                e = entry_list_next(*el))
        {
            e->star->id = NULL;
        }
    }
    
    fors_std_star_list_delete(sl, fors_std_star_delete);
    entry_list_delete(el, entry_delete);
}

/*----------------------------------------------------------------------------*/
/**
 * @brief   Compare two strings while ignoring trailing blanks.
 * @param   s1  String 1
 * @param   s2  String 2
 * @return  true if identic, otherwise false
 * 
 * A NULL input is treated as "".
 */
/*----------------------------------------------------------------------------*/
static bool
fors_fits_compare_string(                   const char  *s1,
                                            const char  *s2)
{
    const char  *m1 = "",
                *m2 = "";
    int         len1,
                len2;
    
    if (s1 != NULL)
        m1 = s1;
    if (s2 != NULL)
        m2 = s2;
    
    len1 = strlen(m1);
    len2 = strlen(m2);
    
    while (len1 > 0 && m1[len1-1] == ' ') len1--;
    while (len2 > 0 && m2[len2-1] == ' ') len2--;
    
    if (len1 != len2)
        return false;
    
    if (strncmp(m1, m2, len1) != 0)
        return false;
    
    return true;
}


/*----------------------------------------------------------------------------*/
/**
 * @brief   Delete a cpl_matrix and set the pointer to NULL.
 * @param   m   Matrix to delete
 * @return  Nothing
 */
/*----------------------------------------------------------------------------*/
static void
fors_matrix_null(                           cpl_matrix    **m)
{
    if (m != NULL)
    {
        cpl_matrix_delete(*m);
        *m = NULL;
    }
}

/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
static void
fors_matrix_append_delete(                  cpl_matrix  **m1,
                                            cpl_matrix  **m2)
{
    if (m1 == NULL || m2 == NULL)
        return;
    
    if (*m2 != NULL)
    {
        if (*m1 == NULL)
        {
            *m1 = *m2;
            *m2 = NULL;
        }
        else
        {
            cpl_matrix_append(*m1, *m2, 0);
            fors_matrix_null(m2);
        }
    }
    /* else do nothing */
}

/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
static double
fors_property_get_num(                      const cpl_property  *prop)
{
    double      retval = 0;
    cpl_type    type;
    
    cassure_automsg(                        prop != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return 0);
    
    type = cpl_property_get_type(prop);
    
    switch (type)
    {
        case CPL_TYPE_BOOL:
            retval = cpl_property_get_bool(prop);
            break;
        case CPL_TYPE_INT:
            retval = cpl_property_get_int(prop);
            break;
        case CPL_TYPE_FLOAT:
            retval = cpl_property_get_float(prop);
            break;
        case CPL_TYPE_DOUBLE:
            retval = cpl_property_get_double(prop);
            break;
        default:
            cpl_error_set_message(  cpl_func, CPL_ERROR_INVALID_TYPE,
                                    "type must be bool, int, float or double");
    }
    
    switch (type)
    {
        case CPL_TYPE_BOOL:
            return (fabs(retval) > 0.5) ? 1 : 0;
        case CPL_TYPE_INT:
            return round(retval);
        default:
            return retval;
    }
}

/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
static double
fors_photometry_parameter_get_num(          const cpl_parameterlist *parameters,
                                            const char              *name,
                                            cpl_type                type)
{
    char  *descriptor;
    double      retval = -1;
    
    cpl_msg_indent_more();
    descriptor = cpl_sprintf("fors.%s.%s", fors_photometry_name, name);
    switch (type)
    {
        case CPL_TYPE_BOOL:
            retval = dfs_get_parameter_bool_const(parameters, descriptor);
            break;
        case CPL_TYPE_INT:
            retval = dfs_get_parameter_int_const(parameters, descriptor);
            break;
/*        case CPL_TYPE_FLOAT:
            retval = dfs_get_parameter_float_const(parameters, descriptor);
            break;*/
        case CPL_TYPE_DOUBLE:
            retval = dfs_get_parameter_double_const(parameters, descriptor);
            break;
        default:
            cpl_error_set_message(  cpl_func, CPL_ERROR_INVALID_TYPE,
                                    "type must be bool, int"
                                    /*", float"*/
                                    " or double");
    }
    cpl_free(descriptor);
    cpl_msg_indent_less();
    
    switch (type)
    {
        case CPL_TYPE_BOOL:
            return (fabs(retval) > 0.5) ? true : false;
        case CPL_TYPE_INT:
            return round(retval);
        default:
            return retval;
    }
}

/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
static const char*
fors_photometry_parameter_get_string(       const cpl_parameterlist *parameters,
                                            const char              *name)
{
    char        *descriptor;
    const char  *retval = NULL;
    
    cpl_msg_indent_more();
    descriptor = cpl_sprintf("fors.%s.%s", fors_photometry_name, name);

    retval = dfs_get_parameter_string_const(parameters, descriptor);
    
    cpl_free(descriptor);
    cpl_msg_indent_less();
    
    return retval;
}

/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
static fors_fit_ncoeff
fors_photometry_parameter_get_ncoeff(       const cpl_parameterlist *parameters,
                                            const char              *name)
{
    const char  *fit_n_str;
    fit_n_str = fors_photometry_parameter_get_string(
                                            parameters,
                                            name);
    if (fit_n_str == NULL)
    {
        cpl_error_set_message(              cpl_func,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "parameter %s not found",
                                            name);
        return -1;
    }
    
    if (strcmp(fit_n_str, fors_fit_ncoeff_paropts.no) == 0)
        return FORS_FIT_NCOEFF_NO;
    else if (strcmp(fit_n_str, fors_fit_ncoeff_paropts.one) == 0)
        return FORS_FIT_NCOEFF_ONE;
    else if (strcmp(fit_n_str, fors_fit_ncoeff_paropts.perframe)== 0)
        return FORS_FIT_NCOEFF_PERFRAME;
    else if (strcmp(fit_n_str, fors_fit_ncoeff_paropts.pernight)== 0)
        return FORS_FIT_NCOEFF_PERNIGHT;
    else
    {
        cpl_error_set_message(              cpl_func,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "unknown parameter value \"%s\" "
                                            "for %s",
                                            fit_n_str,
                                            name);
        return -1;
    }
}

/*----------------------------------------------------------------------------*/
/**
 * @brief    Define recipe parameters
 * @param    parameters     parameter list to fill
 */
/*----------------------------------------------------------------------------*/
void fors_photometry_define_parameters(     cpl_parameterlist   *parameters)
{
    const char *context = cpl_sprintf("fors.%s", fors_photometry_name);

    const char *name, *full_name;
    cpl_parameter *p;

    name = "fitz";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_BOOL,
                                "Fit zeropoint",
                                context,
                                true);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    name = "fit_all_mag";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_BOOL,
                                "Always fit star magnitudes",
                                context,
                                false);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;
    
    name = "fite";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_enum( full_name,
                                CPL_TYPE_STRING,
                                "Fit atmospheric extinctions",
                                context,
                                fors_fit_ncoeff_paropts.pernight,
                                4,
                                fors_fit_ncoeff_paropts.no,
                                fors_fit_ncoeff_paropts.one,
                                fors_fit_ncoeff_paropts.perframe,
                                fors_fit_ncoeff_paropts.pernight);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    name = "fitc";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_BOOL,
                                "Fit color correction term",
                                context,
                                false);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    name = "use_all_stars";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_BOOL,
                                "Use also non-standard stars to fit "
                                "polynomial f",
                                context,
                                false);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    name = "degreef1";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_INT,
                                "FLatfield correction map polynomial degree "
                                "(x)",
                                context,
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    name = "degreef2";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_INT,
                                "Flatfield correction map polynomial degree "
                                "(y), or negative for "
                                "triangular coefficient matrix",
                                context,
                                -1);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    name = "degreep";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_INT,
                                "Extinction/color coupling degree",
                                context,
                                0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name); full_name = NULL;

    cpl_free((void *)context);

    return;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_polynomial_delete(polyf); polyf = NULL; \
} while (0)
/**
 * @brief   Create a polynomial containing the desired coefficients (set to 1).
 * @param   degreef1    Degree in x (>= 0)
 * @param   degreef2    Degree in y,
 *                      or negative for triangular coefficient matrix.
 * @return  The polynomial, NULL in the case of error
 * 
 * @note
 * This function should contain the only definition of the shape of the
 * @em f polynomial (according to the manual).
 */
/*----------------------------------------------------------------------------*/
static cpl_polynomial*
fors_photometry_define_polyf(               int                 degreef1,
                                            int                 degreef2)
{
    int             xpow,
                    ypow;
    cpl_polynomial  *polyf = NULL;
    
    /* free output pointers */
    cleanup;
    
    /* check input */
    assure(!cpl_error_get_code(), return NULL, "Previous error caught.");
    
    if (degreef1 < 0)
    {
        cpl_error_set_message(  cpl_func, CPL_ERROR_ILLEGAL_INPUT,
                                "!(degreef1 >= 0)");
        return NULL;
    }
    
    /* define the polynomial */
    polyf = cpl_polynomial_new(2);  /* 2 dimensions */
    for (xpow = 0; xpow <= degreef1; xpow++)
    {
        for (ypow = 0;
             (degreef2 >= 0) ? (ypow <= degreef2) : (xpow + ypow <= degreef1);
             ypow++)
         {
            if (xpow+ypow > 0)
            {
                cpl_size pows[2];
                pows[0] = xpow;
                pows[1] = ypow;
                cpl_polynomial_set_coeff(polyf, pows, 1.0);
            }
         }
    }
    
    fors_polynomial_dump(polyf, "polynomial definition f", CPL_MSG_DEBUG, NULL);
    
    /* consistency check */
    if (degreef2 >= 0)
    {
        cassure(fors_polynomial_count_coeff(polyf)
                == (degreef1+1)*(degreef2+1)-1,
                CPL_ERROR_UNSPECIFIED,
                return polyf,
                "Consistency check for rectangular f polynomial failed");
    }
    else
    {
        cassure(fors_polynomial_count_coeff(polyf)
                == ((degreef1+1)*(degreef1+2))/2-1,
                CPL_ERROR_UNSPECIFIED,
                return polyf,
                "Consistency check for triangular f polynomial failed");
    }
    
    return polyf;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_polynomial_delete(polyp); polyp = NULL; \
} while (0)
/**
 * @brief   Create a 2-dim polynomial with the desired coefficients (set to 1).
 * @param   degreep     Total Degree
 * @return  The polynomial, NULL in the case of error
 * 
 * The first dimension refers to the airmass, the second to the color.
 * 
 * @note
 * This function should contain the only definition of the shape of the
 * @em p polynomial (according to the manual).
 */
/*----------------------------------------------------------------------------*/
static cpl_polynomial*
fors_photometry_define_polyp(               int                 degreep)
{
    int             k,
                    l;
    cpl_polynomial  *polyp = NULL;
    
    /* free output pointers */
    cleanup;
    
    /* check input */
    assure(!cpl_error_get_code(), return NULL, "Previous error caught.");
    
    cassure(                                degreep >= 0,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return polyp,
                                            "!(degreep >= 0)");
    
    /* define the polynomial */
    polyp = cpl_polynomial_new(2);  /* 2 dimensions */
    
    for (k = 0; k <= degreep; k++) {
        for (l = 0; k + l <= degreep; l++)
            if (k+l > 1) {
                cpl_size pows[2];
                pows[0] = k;
                pows[1] = l;
                cpl_polynomial_set_coeff(polyp, pows, 1.0);
            }
    }
    
    fors_polynomial_dump(polyp, "polynomial definition p", CPL_MSG_DEBUG, NULL);
    
    /* consistency check */
    {
        int npars = degreep >= 2 ? ((degreep+1)*(degreep+2))/2-3 : 0;
        cassure(fors_polynomial_count_coeff(polyp) == npars,
                CPL_ERROR_UNSPECIFIED,
                return polyp,
                "Consistency check for triangular p polynomial failed");
    }
    
    return polyp;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    if (poly != NULL) { cpl_polynomial_delete(*poly); *poly = NULL; } \
    if (var_poly != NULL) {cpl_polynomial_delete(*var_poly); *var_poly = NULL;}\
} while (0)
/**
 * @brief   Create a polynomial from a definition and a coefficients container.
 * @param   p_def       Polynomial definition (using non-zero coeffs)
 * @param   coeffs      Column vector (N x 1) containing the coefficients in the
 *                      order defined by the functions fors_polynomial_*(),
 *                      ignored if @a p_def contains no non-zero coefficients
 * @param   cov_coeffs  (Optional) covariance matrix (N x N) of the coefficients
 *                      in the order defined by the
 *                      functions fors_polynomial_*(),
 *                      ignored if @a p_def contains no non-zero coefficients
 *                      or if @a var_poly == NULL
 * @param   poly        Output polynomial
 * @param   var_poly    (Optional) output variance polynomial
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
fors_photometry_poly_new_from_coefficients( const cpl_polynomial    *p_def,
                                            const cpl_matrix        *coeffs,
                                            const cpl_matrix        *cov_coeffs,
                                            cpl_polynomial          **poly,
                                            cpl_polynomial          **var_poly)
{
    int             n_coeffs;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    /* free output pointers */
    cleanup;
    
    cassure_automsg(                        p_def != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        poly != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    
    n_coeffs = fors_polynomial_count_coeff( p_def);
    cassure_automsg(                        n_coeffs == 0 || coeffs != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        n_coeffs == 0
                                            || var_poly == NULL
                                            || cov_coeffs != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    if (n_coeffs > 0)
    {
        
        cassure_automsg(                    cpl_matrix_get_ncol(coeffs) == 1,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return cpl_error_get_code());
        cassure_automsg(                    cpl_matrix_get_nrow(coeffs)
                                            == n_coeffs,
                                            CPL_ERROR_INCOMPATIBLE_INPUT,
                                            return cpl_error_get_code());
        if (var_poly != NULL)
        {
            cassure_automsg(                cpl_matrix_get_nrow(cov_coeffs)
                                            == n_coeffs,
                                            CPL_ERROR_INCOMPATIBLE_INPUT,
                                            return cpl_error_get_code());
            cassure(                        cpl_matrix_get_nrow(cov_coeffs)
                                            == cpl_matrix_get_ncol(cov_coeffs),
                                            CPL_ERROR_INCOMPATIBLE_INPUT,
                                            return cpl_error_get_code(),
                                            "cov_coeffs is not square");
        }
        *poly = cpl_polynomial_duplicate(   p_def);
        fors_polynomial_set_existing_coeff( *poly,
                                            cpl_matrix_get_data_const(coeffs),
                                            n_coeffs);
        passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
        
        if (var_poly != NULL)
            *var_poly = fors_polynomial_create_variance_polynomial(
                                            p_def,
                                            cov_coeffs);
        passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    }
    else    /* create empty polynomial */
    {
        *poly = cpl_polynomial_new(         cpl_polynomial_get_dimension(
                                                p_def));
        if (var_poly != NULL)
            *var_poly = cpl_polynomial_new( cpl_polynomial_get_dimension(
                                                p_def));
    }
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    return (cpl_errorstate_is_equal(errstat) ?
            CPL_ERROR_NONE :
            cpl_error_get_code());
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Return the timezone of the observer in hours against UT
 * @param   header  Frame header
 * @return  Number of hours in the range [-12 ... +12]
 */
/*----------------------------------------------------------------------------*/
int
fors_photometry_get_timezone_observer(      const cpl_propertylist  *header)
{
    const cpl_property  *prop;
    
    cassure_automsg(                        header != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return 0);
    
    do {
        const char  *origin;
                
        prop = cpl_propertylist_get_property_const(header, "ORIGIN");
        if (prop == NULL)
        {
            cpl_error_set_message(          cpl_func,
                                            CPL_ERROR_DATA_NOT_FOUND,
                                            "Couldn't find the keyword ORIGIN");
            return 0;
        }
        
        if (cpl_property_get_type(prop) != CPL_TYPE_STRING)
            break;
        
        if ((origin = cpl_property_get_string(prop)) == NULL)
            break;
        
        if (!fors_fits_compare_string(origin, "ESO"))
            break;
        
        /* We're at ESO, i.e. in Chile */
        return -3;
        
    } while (0);
    
    cpl_error_set_message(                  cpl_func,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "Don't know the originator of the "
                                            "frame specified in ORIGIN");
    return 0;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Get a unique identifier for the night of the observation.
 * @param   header  Frame header
 * @return  ID
 * 
 * In the case of error, the output ID is undefined.
 */
/*----------------------------------------------------------------------------*/
int
fors_photometry_get_night_id(               const cpl_propertylist *header)
{
    const cpl_property  *prop;
    cpl_errorstate      errstat = cpl_errorstate_get();
    
    cassure_automsg(                        header != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return 0);
    
    /* try to get the Modified Julian Date */
    prop = cpl_propertylist_get_property_const(header, "MJD-OBS");
    if (prop != NULL)
    {
        double  mjd,
                jd,
                timezone;
        int     localstartday;
        mjd = fors_property_get_num(prop);
        assure(                             cpl_errorstate_is_equal(errstat),
                                            return 0,
                                            "Could not interprete Modified "
                                            "Julian Date keyword MJD-OBS");
        
        /* The Julian Calendar starts at noon in Greenwich, counting days.
         * The definition of MJD (FITS standard) is:
         *   MJD = JD - 2'400'000.5
         * The "xxx.5" means it starts some day at midnight (in Greenwich).
         * We want a day definition again that starts at noon, so to have
         * something standard, convert back to Julian date.
         */
         jd = mjd + 2400000.5;
         
         /* Get the timezone of the observation location. The timezone is not
          * perfect to determine sunrise/sunset times, but we don't care
          * since we don't expect any observations +/- 2h around noon.
          */
         timezone = fors_photometry_get_timezone_observer(header);
         
         /* Correct for the timezone. Now we have something like a
          * "Julian Local Time Date" */
         jd += (double)timezone / 24.0;
         
         /* Since the Julian days start every noon, we just round down and
          * have the date of the day in which the night started */
         localstartday = floor(jd);
         cpl_msg_debug(                     cpl_func,
                                            "Julian day no. of observation "
                                            "night: %d",
                                            localstartday);
         
         return localstartday;
    }
    
    /* So far, no alternative for MJD-OBS is provided. If there should be one
     * in future, remember to check for inconsistencies between the frames. */
    cpl_error_set_message(                  cpl_func,
                                            CPL_ERROR_DATA_NOT_FOUND,
                                            "Couldn't find the keyword "
                                            "MJD-OBS");
    return 0;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_free(ident_array); ident_array = NULL; \
} while (0)
/**
 * @brief   Collect all atm. extinction identifiers and create the indices.
 * @param   obs_list    Observation list
 * @param   Number of indices (= max index + 1), 0 in the case of error
 * 
 * The indices are created counting from zero, and in the order their
 * identifiers appear.
 */
/*----------------------------------------------------------------------------*/
static int
fors_photometry_atm_ext_create_index_by_identifier(
                                            entry_list          *obs_list)
{
    entry           *e;
    int             *ident_array;
    int             n_entries,
                    n_idents = 0;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    cassure_automsg(                        obs_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return 0);
    
    n_entries = entry_list_size(obs_list);
    ident_array = cpl_malloc(n_entries * sizeof(*ident_array));
    
    for (   e = entry_list_first(obs_list);
            e != NULL;
            e = entry_list_next(obs_list))
    {
        int     i;
        bool    found = false;
        for (i = 0; i < n_idents && !found; i++)
        {
            if (e->atm_ext_identifier == ident_array[i])
            {
                found = true;
                e->atm_ext_index = i;
            }
        }
        if (!found)
        {
            ident_array[n_idents] = e->atm_ext_identifier;
            e->atm_ext_index = n_idents;
            
            cpl_msg_debug(                  cpl_func,
                                            "Creating atm. extinction index "
                                            "%2d for identifier %d",
                                            n_idents,
                                            ident_array[n_idents]);
            
            n_idents++;
        }
    }
    
    passure(                                cpl_errorstate_is_equal(errstat),
                                            return 0);
    
    cpl_free(ident_array);
    return n_idents;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Create all atmospheric extinction indices.
 * @param   obsl    Observation list
 * @param   fit_e   How many atmospheric extinctions to fit
 * @return  Number of atmospheric extinction indices, -1 in the case of error
 * 
 * The following indices in the entries of @a obs_list are set:
 * - -1:        no index (if none to fit)
 * - 0...N-1:   normal indices
 * - undefined: in the case of error
 * 
 * If @a fit_e is neither FORS_FIT_NCOEFF_NO nor FORS_FIT_NCOEFF_ONE nor
 * FORS_FIT_NCOEFF_PERFRAME, then the atmospheric extinction identifier in the
 * entries of @a obs_list must be set, otherwise the atmospheric extinction
 * indices will contain garbage.
 */
/*----------------------------------------------------------------------------*/
static int
fors_photometry_atm_ext_create_indices(     entry_list      *obsl,
                                            fors_fit_ncoeff fit_e)
{
    entry           *e;
    int             n_atm_ext_indices = 0;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    cassure_automsg(                        obsl != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return -1);
    
    if (fit_e != FORS_FIT_NCOEFF_NO
        && fit_e != FORS_FIT_NCOEFF_ONE
        && fit_e != FORS_FIT_NCOEFF_PERFRAME)
    {
        /* if FORS_FIT_NCOEFF_PERNIGHT or any other future option which
         * has set an identifier */
        n_atm_ext_indices = 
            fors_photometry_atm_ext_create_index_by_identifier(obsl);
    }
    else
    {
        if (fit_e == FORS_FIT_NCOEFF_NO)
        {
            for (e = entry_list_first(obsl); e!=NULL; e = entry_list_next(obsl))
                e->atm_ext_index = -1;
            n_atm_ext_indices = 0;
        }
        else if (fit_e == FORS_FIT_NCOEFF_ONE)
        {
            for (e = entry_list_first(obsl); e!=NULL; e = entry_list_next(obsl))
                e->atm_ext_index = 0;
            n_atm_ext_indices = 1;
        }
        else if (fit_e == FORS_FIT_NCOEFF_PERFRAME)
        {
            for (e = entry_list_first(obsl); e!=NULL; e = entry_list_next(obsl))
            {
                e->atm_ext_index = e->frame_index;
                if (e->frame_index >= n_atm_ext_indices)
                    n_atm_ext_indices = e->frame_index + 1;
            }
        }
    }
    
    if (!cpl_errorstate_is_equal(errstat))
        cpl_error_set_where(cpl_func);
    
    return (cpl_errorstate_is_equal(errstat) ? n_atm_ext_indices : -1);
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    if (frame_printed != NULL) \
    { \
        cpl_free(frame_printed); \
        frame_printed = NULL; \
    } \
} while (0)
/**
 * @brief   Print the frame filenames for each atm. ext. index.
 * @param   obs_list    Observation list
 * @param   frames      Frameset used as reference for printing
 * @param   CPL error code
 */
/*----------------------------------------------------------------------------*/
//static cpl_error_code
static cpl_table *
fors_photometry_atm_ext_print_index_by_framename(
                                            const entry_list    *obs_list,
                                            const cpl_frameset  *frames)
{
    const entry     *e;
    bool            *frame_printed = NULL;
    int             n_frames,
                    ext_index,
                    max_ext_index,
                    n;
    int             row = 0;
    cpl_table      *summary;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    cassure_automsg(                        obs_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return NULL);
    cassure_automsg(                        frames != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return NULL);
    
    n_frames = cpl_frameset_get_size(frames);
    frame_printed = cpl_malloc(n_frames * sizeof(*frame_printed));

    summary = cpl_table_new(n_frames);
    cpl_table_new_column(summary, "filename", CPL_TYPE_STRING);
    cpl_table_new_column(summary, "index", CPL_TYPE_INT);
    
    max_ext_index = -1;
    for (   e = entry_list_first_const(obs_list);
            e != NULL;
            e = entry_list_next_const(obs_list))
    {
        if (e->atm_ext_index > max_ext_index)
            max_ext_index = e->atm_ext_index;
    }
    
    if (max_ext_index >= 0)
        cpl_msg_info(                       cpl_func,
                                            "Assignment of atmospheric "
                                            "extinction indices:");
    
    for (ext_index = 0; ext_index <= max_ext_index; ext_index++)
    {
        bool    first_file = true;
        char    estr[15];
        for (n = 0; n < n_frames; n++)
            frame_printed[n] = false;
        
        cpl_msg_indent_more();
        sprintf(estr, "E_%d:         ", ext_index);
        estr[9] = '\0';
        
        for (   e = entry_list_first_const(obs_list);
                e != NULL;
                e = entry_list_next_const(obs_list))
        {
            if (e->atm_ext_index == ext_index)
            {
                cassure_automsg(            e->frame_index >= 0
                                            && e->frame_index < n_frames,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            {
                                                cpl_msg_indent_less();
                                                return NULL;
                                            });
                
                if (!frame_printed[e->frame_index])
                {
                    const cpl_frame *f;
                    
                    f = cpl_frameset_get_frame_const(frames, e->frame_index);
                    if (first_file)
                    {
                        cpl_msg_info(       cpl_func,
                                            "%s%s",
                                            estr,
                                            cpl_frame_get_filename(f));
                    }
                    else
                    {
                        cpl_msg_info(       cpl_func,
                                            "         %s",
                                            cpl_frame_get_filename(f));
                    }
                    frame_printed[e->frame_index] = true;
                    first_file = false;

                    /*
                     * This tail is added to store the filename / index
                     * in a summary table.
                     */

                    cpl_table_set_string(summary, "filename", row, 
                                         cpl_frame_get_filename(f));
                    cpl_table_set_int(summary, "index", row, ext_index);
                    row++;

                }
            }
            
            
        }
        cpl_msg_indent_less();
    }

//    cpl_table_save(summary, NULL, NULL, "summary.fits", CPL_IO_CREATE);
    
    cleanup;
    
    passure(                                cpl_errorstate_is_equal(errstat),
                                            return NULL);
    
    return summary;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Check input value and its error to stay in a range.
 * @param   value       Input value to check
 * @param   value_error Input value's uncertainty (1 sigma) to check
 * @param   value_name  Input value's name to use in error messages
 * @param   input_name  Input's name (e.g. filename) to use in error messages
 * @param   min_limit   Minimum limit for @a value
 * @param   max_limit   Maximum limit for @a value
 * @param   max_error   Maximum limit for @a value_error
 * @return  CPL error code
 * 
 * As a minimum limit for @a value_error, 0 is supposed.
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
fors_photometry_check_input_value(          double      value,
                                            double      value_error,
                                            const char  *value_name,
                                            const char  *input_name,
                                            double      min_limit,
                                            double      max_limit,
                                            double      max_error)
{
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    if (value < min_limit || value > max_limit)
    {
        cpl_error_set_message(              cpl_func,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "invalid %s (%f)"
                                            "%s%s"
                                            ", either correct input, or try to "
                                            "re-run this recipe with fitting "
                                            "%s enabled",
                                            value_name,
                                            value,
                                            (input_name != NULL)?" read from ":"",
                                            (input_name != NULL)?input_name:"",
                                            value_name);
    }
    else if (value_error > max_error || value_error < 0)
    {
        char    exceed_max_err[30];
        sprintf(exceed_max_err, "> %f", max_error);
        cpl_error_set_message(              cpl_func,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "unreliable %s ((error = %g) %s)"
                                            "%s%s"
                                            ", either recompute input, or try "
                                            "to re-run this recipe with "
                                            "fitting %s enabled",
                                            value_name,
                                            value_error,
                                            (value_error < 0) ?
                                                "< 0" : exceed_max_err,
                                            (input_name != NULL)?" read from ":"",
                                            (input_name != NULL)?input_name:"",
                                            value_name);
    }
    else
    {
        cpl_msg_info(                       cpl_func,
                                            "Using input value%s%s: "
                                            "%s = %f +- %f",
                                            (input_name != NULL)?" from ":"",
                                            (input_name != NULL)?input_name:"",
                                            value_name,
                                            value,
                                            value_error);
    }
    
    return (cpl_errorstate_is_equal(errstat) ?
                CPL_ERROR_NONE :
                cpl_error_get_code());
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_array_delete(airmasses); airmasses = NULL; \
} while (0)
/**
 * @brief   Check whether fitting is possible (see below).
 * @param   fit_e   How many atmospheric extinctions to fit
 * @param   fit_z   Flag whether to fit the zeropoint
 * @return  CPL error code
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
fors_photometry_check_fitparam_atm_ext(     entry_list      *obsl,
                                            fors_fit_ncoeff fit_e,
                                            bool            fit_z)
{
    entry           *e;
    int             n_atm_ext_indices = 0;
    cpl_array       *airmasses = NULL;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    cassure_automsg(                        obsl != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    
    /* we only have a problem if we want to fit the zeropoint and the
     * atmospheric extinction at the same time, but there are not
     * enough airmasses */
    if (!fit_z || fit_e == FORS_FIT_NCOEFF_NO)
    {
        return CPL_ERROR_NONE;
    }
    
    /* count the indices */
    for (e = entry_list_first(obsl); e!=NULL; e = entry_list_next(obsl))
    {
        if (e->atm_ext_index >= n_atm_ext_indices)
            n_atm_ext_indices = e->atm_ext_index + 1;
    }
    
    /*assure(                                 cpl_errorstate_is_equal(errstat),
                                            return cpl_error_get_code(),
                                            NULL);*/

    /* Check whether there are at least 2 different airmasses for
     * at least 1 atmospheric extinction
     */
    if (n_atm_ext_indices > 0)
    {
        bool    multiple_found = false;
        
        airmasses = cpl_array_new(n_atm_ext_indices, CPL_TYPE_DOUBLE);
        
        for (   e = entry_list_first(obsl);
                e != NULL;
                e = entry_list_next(obsl))
        {
            double  first_airmass;
            int     is_set;
            first_airmass = cpl_array_get_double(
                                            airmasses,
                                            e->atm_ext_index,
                                            &is_set); 
            passure(                        cpl_errorstate_is_equal(errstat),
                                            return cpl_error_get_code());
            is_set = (is_set == 0);
            if (!is_set)
                cpl_array_set_double(airmasses, e->atm_ext_index, e->airmass);
            else
            {
                /* if there is a different airmass for this ext index */
                if (fabs(e->airmass - first_airmass) > 10*DBL_EPSILON)
                {
                    multiple_found = true;
                    break;
                }
                /* we won't check here whether the difference in airmass is
                 * too small to get reliable results, that will turn out
                 * in the errors of the output after fitting */
            }
        }
        
        if (!multiple_found)
        {
            if (n_atm_ext_indices > 1)
                cpl_msg_error(              cpl_func,
                                            "No atmospheric extinction was "
                                            "observed at different airmasses.");
            else
                cpl_msg_error(              cpl_func,
                                            "Atmospheric extinction was not "
                                            "observed at different airmasses.");
        }
        cassure(                            multiple_found,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return cpl_error_get_code(),
                                            "For fitting the zeropoint and "
                                            "atmospheric extinction, "
                                            "there must be >= 2 different "
                                            "airmasses for at least 1 "
                                            "atmospheric extinction");
    }
    
    cpl_array_delete(airmasses);
    
    return (cpl_errorstate_is_equal(errstat) ?
            CPL_ERROR_NONE :
            cpl_error_get_code());
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    fors_matrix_null(&solution); \
    fors_matrix_null(&cov1); \
    fors_matrix_null(&At); \
    fors_matrix_null(&AtC); \
    fors_matrix_null(&AtCA); \
    fors_matrix_null(&p); \
    fors_matrix_null(&Ap); \
    fors_matrix_null(&bAp); \
    fors_matrix_null(&bApt); \
    fors_matrix_null(&C1bAp); \
    fors_matrix_null(&chi2); \
} while (0)
/* 
   @brief Linear correlated weighted least squares fit
   @param coeff      design matrix (A)
   @param rhs        right hand side (b)
   @param cov_rhs    covariance of b (C)
   @param red_chisq  (output) reduced chi squared, or NULL

   Similar to cpl_matrix_solve_normal, except the output matrix is not
   a m x 1 matrix

   x0
   x1
   x2
   :

   but an m x (m+1) matrix

   x0  C00 C01 C02 ...
   x1  C10 C11 
   x2  C20     . 
   :   :        .

   where the first column is the least chi squared solution to the
   overdetermined equation system Ax = b, given the covariance, C, of b.

   and the last m columns is the covariance matrix of the solution
   (A^t C^-1 A)^-1

   The reduced chi squared is given by
   
     (b-Ap)^t C^-1 (b-Ap) / (degrees of freedom)

   where  degrees of freedom = |b| - |p|

 */
/*----------------------------------------------------------------------------*/
static cpl_matrix *
solve_normal(const cpl_matrix *coeff, 
             const cpl_matrix *rhs,
             const cpl_matrix *cov_rhs,
             double *red_chisq)
{
    cpl_matrix      *solution = NULL;
    cpl_matrix      *cov1 = NULL;  /* C^-1 */
    cpl_matrix      *At = NULL;    /* A^t */
    cpl_matrix      *AtC = NULL;   /* A^t C^-1 */
    cpl_matrix      *AtCA = NULL;  /* A^t C^-1 A */
    cpl_matrix      *p = NULL;
    cpl_matrix      *Ap = NULL;
    cpl_matrix      *bAp = NULL;
    cpl_matrix      *bApt = NULL;
    cpl_matrix      *C1bAp = NULL;
    cpl_matrix      *chi2 = NULL;
    cpl_error_code  error;

    cpl_ensure(coeff   != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(rhs     != NULL, CPL_ERROR_NULL_INPUT, NULL);
    cpl_ensure(cov_rhs != NULL, CPL_ERROR_NULL_INPUT, NULL);

    /* The overall time is probably dominated by this
       matrix inversion which is O(n^3) if C is nxn */
    cov1 = cpl_matrix_invert_create(cov_rhs);

    assure( cov1 != NULL, return NULL, 
            "Could not invert covariance matrix. Make sure that provided "
            "errors are positive");
    /* The covariance matrix is singular if one (or more) eigenvalue is
       zero, i.e. if there exist a unitary transformation (rotation of
       coordinates) that makes the variance of one of the new coordinates
       zero (and therefore a failure at this place is probably because the
       user has provided some non-positive error).
    */
    
    At  = cpl_matrix_transpose_create(coeff);

    AtC = cpl_matrix_product_create(At, cov1);   

    fors_matrix_null(&At);

    AtCA = cpl_matrix_product_create(AtC, coeff);
    
    solution = cpl_matrix_product_create(AtC, rhs);
    cpl_matrix_set_size(solution, 
                        cpl_matrix_get_nrow(solution),
                        1 + cpl_matrix_get_nrow(solution));
    {
        int i = 0;
        for (i = 0; i < cpl_matrix_get_nrow(solution); i++) {
            cpl_matrix_set(solution, i, i+1, 1);
        }
    }

    fors_matrix_null(&AtC);

    //cpl_matrix_dump(AtCA, stdout);
    //cpl_matrix_dump(solution, stdout);
    
    error = cpl_matrix_decomp_chol(AtCA);
    if (!error) {
        error = cpl_matrix_solve_chol(AtCA, solution);
    }
    
    fors_matrix_null(&AtCA);

    if (error) {
        cleanup;
        cpl_ensure(0, error, NULL);
    }


    if (red_chisq != NULL) {

        /* Get first column vector, p, of solution */
        p = cpl_matrix_duplicate(solution);
        cpl_matrix_set_size(p, cpl_matrix_get_nrow(p), 1);
        
        Ap = cpl_matrix_product_create(coeff, p);

        bAp = cpl_matrix_duplicate(rhs);
        cpl_matrix_subtract(bAp, Ap);

        bApt = cpl_matrix_transpose_create(bAp);

        C1bAp = cpl_matrix_product_create(cov1, bAp);

        chi2 =  cpl_matrix_product_create(bApt, C1bAp);

        passure(cpl_matrix_get_nrow(chi2) == 1 &&
                cpl_matrix_get_ncol(chi2) == 1, return NULL);

        *red_chisq = cpl_matrix_get(chi2, 0, 0) /
            (cpl_matrix_get_nrow(rhs) - cpl_matrix_get_nrow(p));

    }
    
    cpl_matrix  *return_solution = solution; solution = NULL;
    cleanup;

    return return_solution;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_frameset_delete((cpl_frameset *)aligned_phot_frames);        \
    cpl_frameset_delete((cpl_frameset *)master_flat_frame); \
    cpl_frameset_delete((cpl_frameset *)phot_table); \
    fors_setting_delete(&setting); \
    fors_image_delete(&master_flat); \
    fors_image_delete(&correction); \
    cpl_table_delete(phot_coeff); \
    cpl_table_delete(summary); \
    fors_delete_star_lists(&obs, &std_star_list); \
    cpl_array_delete(n_std_star_obs); n_std_star_obs = NULL; \
    fors_matrix_null(&eqn_lhs); \
    fors_matrix_null(&eqn_rhs); \
    fors_matrix_null(&eqn_cov_rhs); \
    fors_matrix_null(&eqn_result); \
    fors_matrix_null(&tmp_mat); \
    fors_matrix_null(&result_polyf); \
    fors_matrix_null(&result_cov_polyf); \
    fors_matrix_null(&result_params); \
    fors_matrix_null(&result_cov_params); \
    fors_matrix_null(&result_polyp); \
    fors_matrix_null(&result_cov_polyp); \
    cpl_polynomial_delete(polyf); polyf = NULL; \
    cpl_polynomial_delete(polyf_definition); polyf_definition = NULL; \
    cpl_polynomial_delete(polyf_variance); polyf_variance = NULL; \
    cpl_polynomial_delete(polyp); polyp = NULL; \
    cpl_polynomial_delete(polyp_definition); polyp_definition = NULL; \
} while (0)

/**
 * @brief    Do the processing
 *
 * @param    frames         input frames
 * @param    parameters     recipe parameters
 *
 * @return   0 if everything is ok
 */
/*----------------------------------------------------------------------------*/
void fors_photometry(cpl_frameset *frames, const cpl_parameterlist *parameters)
{
    /* Input */
    const cpl_frameset  *aligned_phot_frames = NULL,
                        *master_flat_frame   = NULL,
                        *phot_table          = NULL;
    fors_image          *master_flat         = NULL;

    /* Products */
    fors_image          *correction = NULL;
    cpl_table           *phot_coeff = NULL;
    cpl_table           *summary    = NULL;
    
    /* Star lists */
    fors_std_star_list  *std_star_list = NULL;
    entry_list          *obs = NULL;
    cpl_array           *n_std_star_obs = NULL;
    
    /* Equation system */
    cpl_matrix          *eqn_lhs = NULL,
                        *eqn_rhs = NULL,
                        *eqn_cov_rhs = NULL,
                        *eqn_result = NULL,
                        *result_polyf = NULL,
                        *result_cov_polyf = NULL,
                        *result_params = NULL,
                        *result_cov_params = NULL,
                        *result_polyp = NULL,
                        *result_cov_polyp = NULL,
                        *tmp_mat = NULL;
    
    /* polynomials to fit */
    cpl_polynomial      *polyf = NULL,
                        *polyf_definition = NULL,
                        *polyf_variance = NULL,
                        *polyp = NULL,
                        *polyp_definition = NULL;
    
    /* Other */
    fors_setting        *setting = NULL;
    const char          *tag     = NULL;
    int                  row;

    /* Photometric parameters */
    cpl_propertylist *qc        = NULL;
    double qc_zeropoint         = -1.0;
    double qc_zeropoint_err     = -1.0;
    double qc_extinction        = -1.0;
    double qc_extinction_err    = -1.0;
    double qc_colorterm         = -1.0;
    double qc_colorterm_err     = -1.0;
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Find input */
    aligned_phot_frames = fors_frameset_extract(frames, ALIGNED_PHOT);
    cassure(cpl_frameset_get_size(aligned_phot_frames) > 0,
            CPL_ERROR_DATA_NOT_FOUND,
            return, 
            "No %s provided", ALIGNED_PHOT);

    master_flat_frame = fors_frameset_extract(frames, MASTER_SKY_FLAT_IMG);
    cassure(cpl_frameset_get_size(master_flat_frame) > 0,
            CPL_ERROR_DATA_NOT_FOUND,
            return,
            "No %s provided", MASTER_SKY_FLAT_IMG);
    
    phot_table = fors_frameset_extract(frames, PHOT_TABLE);
    cassure(cpl_frameset_get_size(phot_table) == 1,
            CPL_ERROR_DATA_NOT_FOUND,
            return, 
            "One %s required. %"CPL_SIZE_FORMAT" found",
            PHOT_TABLE, cpl_frameset_get_size(phot_table));

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Get command line parameters */
    bool            fit_z,
                    override_fit_m,
                    fit_c,
                    use_all;
    int             degreef1,
                    degreef2,
                    degreep;
    fors_fit_ncoeff fit_e;
    
    degreef1 = fors_photometry_parameter_get_num(
                                            parameters,
                                            "degreef1",
                                            CPL_TYPE_INT);
    degreef2 = fors_photometry_parameter_get_num(
                                            parameters,
                                            "degreef2",
                                            CPL_TYPE_INT);
    degreep = fors_photometry_parameter_get_num(
                                            parameters,
                                            "degreep",
                                            CPL_TYPE_INT);
    fit_z = fors_photometry_parameter_get_num(
                                            parameters,
                                            "fitz",
                                            CPL_TYPE_BOOL);
    override_fit_m = fors_photometry_parameter_get_num(
                                            parameters,
                                            "fit_all_mag",
                                            CPL_TYPE_BOOL);
    fit_c = fors_photometry_parameter_get_num(
                                            parameters,
                                            "fitc",
                                            CPL_TYPE_BOOL);
    use_all = fors_photometry_parameter_get_num(
                                            parameters,
                                            "use_all_stars",
                                            CPL_TYPE_BOOL);
    fit_e = fors_photometry_parameter_get_ncoeff(
                                            parameters,
                                            "fite");
    assure( !cpl_error_get_code(), return, NULL );
    
    if (fit_e == FORS_FIT_NCOEFF_PERFRAME
        && fit_z == true)
    {
        cpl_error_set_message(              cpl_func,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            "Fitting one atmospheric "
                                            "extinction per frame and the "
                                            "zeropoint is ambiguous and "
                                            "therefore not possible");
        return;
    }
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Get instrument and filter settings */
    
    setting = fors_setting_new(             cpl_frameset_get_first_const(
                                                aligned_phot_frames));
    assure(                                 !cpl_error_get_code(),
                                            return,
                                            "Could not get instrument setting");

    /* Load filter coefficients */
    struct phot_input {
        double  color_coeff,
                dcolor_coeff,
                ext,
                dext,
                zpoint,
                dzpoint;
    } phot_input;

    phot_input.color_coeff = 0.0;
    phot_input.dcolor_coeff = 0.0;
    phot_input.ext = 0.0;
    phot_input.dext = 0.0;
    phot_input.zpoint = 0.0;
    phot_input.dzpoint = 0.0;

    cpl_msg_info(cpl_func, "Loading photometry table");
    fors_phot_table_load(                   cpl_frameset_get_first_const(
                                                phot_table),
                                            setting,
                                            fit_c ? NULL 
                                                  : &phot_input.color_coeff,
                                            fit_c ? NULL
                                                  : &phot_input.dcolor_coeff,
                                            fit_e != FORS_FIT_NCOEFF_NO ? NULL
                                                  : &phot_input.ext,
                                            fit_e != FORS_FIT_NCOEFF_NO ? NULL
                                                  : &phot_input.dext,
                                            fit_z ? NULL
                                                  : &phot_input.zpoint,
                                            fit_z ? NULL
                                                  : &phot_input.dzpoint);
    assure(                                 !cpl_error_get_code(),
                                            return,
                                            "Could not load photometry table");
    
    /* Check fixed photometric input */
    cpl_msg_indent_more();
    if (!fit_c)
    {
        fors_photometry_check_input_value(  phot_input.color_coeff,
                                            phot_input.dcolor_coeff,
                                            "color correction term",
                                            NULL/*"photometry table"*/,
                                            -10,/* min limit */
                                            10, /* max limit */
                                            1); /* max error */
    }
    if (fit_e == FORS_FIT_NCOEFF_NO)
    {
        fors_photometry_check_input_value(  phot_input.ext,
                                            phot_input.dext,
                                            "atmospheric extinction",
                                            NULL/*"photometry table"*/,
                                            0,  /* min limit */
                                            5,  /* max limit */
                                            1); /* max error */
    }
    if (!fit_z)
    {
        fors_photometry_check_input_value(  phot_input.zpoint,
                                            phot_input.dzpoint,
                                            "zeropoint",
                                            NULL/*"photometry table"*/,
                                            0,  /* min limit */
                                            50, /* max limit */
                                            1); /* max error */
    }
    cpl_msg_indent_less();
    if (cpl_error_get_code() != CPL_ERROR_NONE)
        return;
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Read input observation table */
    int                 n_mag_fits,
                        n_frames;
    int                 (*get_atm_ext_id_func)(const cpl_propertylist*);
    
    cpl_msg_info(                           cpl_func,
                                            "Importing %s tables:",
                                            ALIGNED_PHOT);
    cpl_msg_indent_more();
    
    switch (fit_e)
    {
        case FORS_FIT_NCOEFF_PERNIGHT:
            get_atm_ext_id_func = fors_photometry_get_night_id;
            break;
        default:
            get_atm_ext_id_func = NULL;
    }
    
    obs = fors_photometry_read_input(       aligned_phot_frames,
                                            setting,
                                            get_atm_ext_id_func,
                                            use_all,
                                            &n_frames,
                                            &std_star_list,
                                            degreef1 < 1);
    assure(!cpl_error_get_code(), return, NULL);
    
    fors_photometry_adjust_fit_mag_flags(   std_star_list,
                                            obs,
                                            override_fit_m,
                                            &n_mag_fits);
    assure(!cpl_error_get_code(), return, NULL);
    
    fors_photometry_atm_ext_create_indices( obs,
                                            fit_e);
    passure(!cpl_error_get_code(), return);
    
    if (fit_e != FORS_FIT_NCOEFF_NO && fit_e != FORS_FIT_NCOEFF_ONE)
    {
        summary = fors_photometry_atm_ext_print_index_by_framename(
                                            obs,
                                            aligned_phot_frames);
        assure(!cpl_error_get_code(), return, "Internal error" );
    }
    
    fors_photometry_remove_unnecessary(     std_star_list,
                                            obs,
                                            &n_mag_fits);
    assure(!cpl_error_get_code(), return, NULL);
    
    fors_photometry_check_fitparam_atm_ext(obs, fit_e, fit_z);
    assure(!cpl_error_get_code(), return, NULL);
    
    entry_list_print(obs, CPL_MSG_DEBUG);

    {
        int ntot,
            n_std_stars;
        ntot = entry_list_size(obs);
        n_std_stars = fors_std_star_list_size(std_star_list);
        
        cpl_msg_info(cpl_func, 
                     "Found %d table%s, %d star%s, %d unique star%s, "
                     "%d magnitude%s to determine",
                     n_frames,      n_frames != 1 ? "s" : "",
                     ntot,          ntot != 1 ? "s" : "", 
                     n_std_stars,   n_std_stars != 1 ? "s" : "",
                     n_mag_fits,    n_mag_fits != 1 ? "s" : "");
    }
    cpl_msg_indent_less();

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Build equation system */
    int         n_coeff_polyf = 0,
                n_coeff_polyp = 0,
                n_coeff_params = 0,
                n_coeff_params_ext = 0;
    fors_matrix_null(&eqn_lhs);
    
    polyf_definition = fors_photometry_define_polyf(
                                            degreef1,
                                            degreef2);
    polyp_definition = fors_photometry_define_polyp(
                                            degreep);
    assure(!cpl_error_get_code(), return, NULL);
    
    /* Left hand side */
    tmp_mat = build_equations_lhs_matrix_from_poly(
                                            obs,
                                            polyf_definition,
                                            "f",
                                            &entry_get_powers_x_y);
    assure(!cpl_error_get_code(), return, NULL);
    if (tmp_mat != NULL) n_coeff_polyf = cpl_matrix_get_ncol(tmp_mat);
    fors_matrix_append_delete(&eqn_lhs, &tmp_mat);
    
    tmp_mat = build_equations_lhs_matrix_from_parameters(
                                            obs,
                                            std_star_list,
                                            fit_z,
                                            fit_c,
                                            &n_coeff_params_ext);
    assure(!cpl_error_get_code(), return, NULL);
    if (tmp_mat != NULL) n_coeff_params = cpl_matrix_get_ncol(tmp_mat);
    fors_matrix_append_delete(&eqn_lhs, &tmp_mat);
    
    tmp_mat = build_equations_lhs_matrix_from_poly(
                                            obs,
                                            polyp_definition,
                                            "p",
                                            &entry_get_powers_airmass_color);
    assure(!cpl_error_get_code(), return, NULL);
    if (tmp_mat != NULL) n_coeff_polyp = cpl_matrix_get_ncol(tmp_mat);
    fors_matrix_append_delete(&eqn_lhs, &tmp_mat);
    
    /* Right hand side */
    build_equations_rhs_cov(                obs,
                                            std_star_list,
                                            fit_z,
                                            fit_c,
                                            (n_coeff_params_ext > 0),/* fit_e */
                                            phot_input.color_coeff,
                                            phot_input.dcolor_coeff,
                                            phot_input.ext,
                                            phot_input.dext,
                                            phot_input.zpoint,
                                            phot_input.dzpoint,
                                            &eqn_rhs,
                                            &eqn_cov_rhs);
    /*cpl_msg_info(cpl_func, "lhs");
    cpl_matrix_dump(eqn_lhs, stdout);
    cpl_msg_info(cpl_func, "rhs");
    cpl_matrix_dump(eqn_rhs, stdout);
    cpl_msg_info(cpl_func, "cov_rhs");
    cpl_matrix_dump(eqn_cov_rhs, stdout);*/
    
    assure( !cpl_error_get_code(), return, "Could not setup rhs equations" );
    
    int n_parameters = cpl_matrix_get_ncol(eqn_lhs);
    fors_matrix_null(&tmp_mat);
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Solve the equation system */
    {
        int eqn_nrows = cpl_matrix_get_nrow(eqn_lhs);
        cpl_msg_info(cpl_func, "Solving %d equation%s for %d parameter%s",
                     eqn_nrows,  eqn_nrows != 1 ? "s" : "",
                     n_parameters, n_parameters != 1 ? "s" : "");
    }

    /* Solve this overdetermined set of equations in the least chi squared
       sense using Cholesky-decomposition, output matrix
       is the solution vector (1st column) and the covariance matrix
       in the remaining columns.
    */
    double red_chisq;
    eqn_result = solve_normal(              eqn_lhs,
                                            eqn_rhs,
                                            eqn_cov_rhs,
                                            &red_chisq);
    fors_matrix_null(&eqn_lhs);
    fors_matrix_null(&eqn_rhs);
    fors_matrix_null(&eqn_cov_rhs);

    assure( !cpl_error_get_code(), return, "Could not solve equation system");

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Extract the partial results, propagate polynomial error */
    
    /* Print solution, convert to CPL polynomial */
    int offset = 0;
    {
        int size = n_coeff_polyf;
        if (size > 0)
        {
            result_polyf = cpl_matrix_extract(
                                            eqn_result,
                                            offset, 0,
                                            1, 1,
                                            size, 1);
            result_cov_polyf = cpl_matrix_extract(
                                            eqn_result,
                                            offset, 1+offset,
                                            1, 1,
                                            size, size);
            offset += size;
        }
    }
    {
        int size = n_coeff_params;
        if (size > 0)
        {
            result_params = cpl_matrix_extract(
                                            eqn_result,
                                            offset, 0,
                                            1, 1,
                                            size, 1);
            result_cov_params = cpl_matrix_extract(
                                            eqn_result,
                                            offset, 1+offset,
                                            1, 1,
                                            size, size);
        }
        offset += size;
    }
    {
        int size = n_coeff_polyp;
        if (size > 0)
        {
            result_polyp = cpl_matrix_extract(
                                            eqn_result,
                                            offset, 0,
                                            1, 1,
                                            size, 1);
            result_cov_polyp = cpl_matrix_extract(eqn_result,
                                            offset, 1+offset,
                                            1, 1,
                                            size, size);
        }
        offset += size;
    }
    passure(!cpl_error_get_code(), return);
    
    fors_photometry_poly_new_from_coefficients(
                                            polyf_definition,
                                            result_polyf,   /* NULL if f empty*/
                                            result_cov_polyf,/* NULL if f emp.*/
                                            &polyf,
                                            &polyf_variance);
    passure(!cpl_error_get_code(), return);
    fors_photometry_poly_new_from_coefficients(
                                            polyp_definition,
                                            result_polyp,   /* NULL if p empty*/
                                            NULL,/* NULL if p emp.*/
                                            &polyp,
                                            NULL);
    passure(!cpl_error_get_code(), return);
    
    cpl_msg_indent_more();
    fors_polynomial_dump(polyf, "f", CPL_MSG_INFO, polyf_definition);
    fors_polynomial_dump(polyp, "p", CPL_MSG_INFO, polyp_definition);
    cpl_msg_indent_less();
    
    {
        /* magnitudes */
        int                 k,
                            i = 0;
        const fors_std_star *std;
        
        /* count observations of std star objects */
        n_std_star_obs = fors_photometry_count_observations(
                                            std_star_list,
                                            obs);
        passure(!cpl_error_get_code(), return);
        
        cpl_msg_indent_more();
        for (   std = fors_std_star_list_first_const(std_star_list), k = 0;
                std != NULL;
                std = fors_std_star_list_next_const(std_star_list), k++)
        {
            if (std->trusted)   /* !(fit magnitude) */
            {
                cpl_msg_info(cpl_func, "M%d = %f +- %f mag (fixed, %s, %d obs)",
                             k,
                             std->magnitude,
                             std->dmagnitude,
                             std->name,
                             cpl_array_get_int(n_std_star_obs, k, NULL));
            }
            else {
                cpl_msg_info(cpl_func, "M%d = %f +- %f mag (free, %s, %d obs)",
                             k,
                             cpl_matrix_get(result_params, i, 0),
                             sqrt(cpl_matrix_get(result_cov_params, i, i)),
                             std->name,
                             cpl_array_get_int(n_std_star_obs, k, NULL));
                i++;
            }
        }
        cpl_msg_indent_less();
        passure(!cpl_error_get_code(), return);
        
        cpl_array_delete(n_std_star_obs); n_std_star_obs = NULL;
        fors_std_star_list_delete(&std_star_list, fors_std_star_delete);
        entry_list_delete(&obs, entry_delete_but_standard); obs = NULL;

        /* zeropoint */
        cpl_msg_indent_more();
        if (fit_z)
        {
            qc_zeropoint = cpl_matrix_get(result_params, i, 0);
            qc_zeropoint_err = sqrt(cpl_matrix_get(result_cov_params, i, i));
            cpl_msg_info(cpl_func, "Z = %f +- %f mag",
                         qc_zeropoint,
                         qc_zeropoint_err);
            i++;
        }
        cpl_msg_indent_less();
        passure(!cpl_error_get_code(), return);
        
        /* extinction */
        cpl_msg_indent_more();
        if (n_coeff_params_ext > 0)
        {
            qc_extinction = cpl_matrix_get(result_params, i, 0);
            qc_extinction_err = sqrt(cpl_matrix_get(result_cov_params, i, i));
            if (n_coeff_params_ext == 1)
            {
                cpl_msg_info(cpl_func, "E = %f +- %f mag/airmass",
                             qc_extinction,
                             qc_extinction_err);
                i++;
            }
            else
            {
                if (summary) {
                    cpl_table_new_column(summary, "EXT", CPL_TYPE_DOUBLE);
                    cpl_table_new_column(summary, "DEXT", CPL_TYPE_DOUBLE);
                    if (fit_e == FORS_FIT_NCOEFF_PERFRAME) {
                        cpl_table_new_column(summary, "MJD-OBS", 
                                             CPL_TYPE_DOUBLE);
                    }
                    if (fit_e == FORS_FIT_NCOEFF_PERNIGHT) {
                        cpl_table_new_column(summary, "MJD-NIGHT", 
                                             CPL_TYPE_INT);
                    }
                    
                }

                for (k = 0; k < n_coeff_params_ext; k++)
                {
                    double ext  = cpl_matrix_get(result_params, i, 0);
                    double dext = sqrt(cpl_matrix_get(result_cov_params, i, i));

                    cpl_msg_info(cpl_func, "E_%d = %f +- %f mag/airmass",
                                 k, ext, dext);

                    if (summary) {
                        cpl_table_select_all(summary);
                        cpl_table_and_selected_int(summary, "index",
                                                   CPL_EQUAL_TO, k);
                        for (row = 0; row < n_frames; row++) {
                            if (cpl_table_is_selected(summary, row)) {
                                const char *filename =
                                cpl_table_get_string(summary, "filename", row);
                                cpl_propertylist *plist = 
                                cpl_propertylist_load_regexp(filename, 0,
                                                             "MJD-OBS|ORIGIN", 
                                                             0);

                                cpl_table_set_double(summary, 
                                                     "EXT", row, ext);
                                cpl_table_set_double(summary, 
                                                     "DEXT", row, dext);
                                if (fit_e == FORS_FIT_NCOEFF_PERFRAME) {
                                    cpl_table_set_double(summary, 
                                                         "MJD-OBS", row, 
                                       cpl_propertylist_get_double(plist, 
                                                                   "MJD-OBS"));
                                }
                                if (fit_e == FORS_FIT_NCOEFF_PERNIGHT) {
                                    cpl_table_set_int(summary, "MJD-NIGHT", 
                                                      row, 
                                         fors_photometry_get_night_id(plist));
                                }

                                cpl_propertylist_delete(plist);
                            }
                        }
                    }

                    i++;
                }
            }
        }
        cpl_msg_indent_less();
        passure(!cpl_error_get_code(), return);

        if (summary) {
            cpl_table_select_all(summary);
            cpl_table_erase_column(summary, "index");
        }

        /* color */
        cpl_msg_indent_more();
        if (fit_c) {
            /* Note different sign convention. The values
               provided in PHOT_TABLEs are actually -c.
               As in fors_std_cat_load() 
            */
            qc_colorterm = cpl_matrix_get(result_params, i, 0);
            qc_colorterm = -qc_colorterm;   /* External convention */
            qc_colorterm_err = sqrt(cpl_matrix_get(result_cov_params, i, i));
            cpl_msg_info(cpl_func, "C_correction = %f +- %f",
                         qc_colorterm, qc_colorterm_err);
            i++;
        }
        cpl_msg_indent_less();
        passure(!cpl_error_get_code(), return);

        /* Abort if crazy values... */
        if (qc_zeropoint_err > 1.0) {
            cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                  "Unreliable zeropoint!");
            cleanup;
            return;
        }
        if (qc_extinction_err > 1.0) {
            cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                  "Unreliable atmospheric extinction!");
            cleanup;
            return;
        }
        if (qc_colorterm_err > 1.0) {
            cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                  "Unreliable color correction term!");
            cleanup;
            return;
        }
        if (qc_extinction_err > 0.0 && qc_extinction <= 0.0) {
            cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
                                  "Impossible atmospheric extinction!");
            cleanup;
            return;
        }
        passure(!cpl_error_get_code(), return);

        cpl_msg_indent_more();
        cpl_msg_info(cpl_func, "Reduced chi square = %f", red_chisq);
        cpl_msg_indent_less();
    
    }
    passure(!cpl_error_get_code(), return);
    /* Code checks:
     * - polynomials must exist (also if empty), they might be used later */
    passure(polyf != NULL, return);
    passure(polyp != NULL, return);

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Compute correction image, apply to master flat */
    {
        int nx,
            ny;
        
        master_flat = fors_image_load(      cpl_frameset_get_first_const(
                                                master_flat_frame),
                                            NULL, setting, NULL);
        assure( !cpl_error_get_code(), return, "Could not load master flat");
        
        nx = fors_image_get_size_x(master_flat);
        ny = fors_image_get_size_y(master_flat);
        cpl_image *correction_map   = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
        cpl_image *correction_map_v = cpl_image_new(nx, ny, CPL_TYPE_FLOAT);
        
        cpl_msg_info(cpl_func, "Creating correction map (magnitude)");
        cpl_image_fill_polynomial(correction_map, polyf,
                                  1.0, 1.0, 1.0, 1.0);
        passure(!cpl_error_get_code(), return);
        cpl_image_fill_polynomial(correction_map_v, polyf_variance,
                                  1.0, 1.0, 1.0, 1.0);
        passure(!cpl_error_get_code(), return);
        
        correction = fors_image_new(correction_map, correction_map_v);
    }
    passure(!cpl_error_get_code(), return);
    
    cpl_polynomial_delete(polyf); polyf = NULL;
    cpl_polynomial_delete(polyf_variance); polyf_variance = NULL;
    fors_matrix_null(&eqn_result);

    if (qc_zeropoint_err > 0.0 ||
        qc_extinction_err > 0.0 ||
        qc_colorterm_err > 0.0) {

        phot_coeff = fors_phot_coeff_create(setting,
                                            qc_colorterm,
                                            qc_colorterm_err,
                                            qc_extinction,
                                            qc_extinction_err,
                                            qc_zeropoint,
                                            qc_zeropoint_err);

        /*
         * Write QCs
         */

        qc = cpl_propertylist_new();

        fors_qc_start_group(qc, fors_qc_dic_version, setting->instrument);

/*
    fors_qc_write_group_heading(cpl_frameset_get_first_const(master_flat_frame),
                                CORRECTION_MAP,
                                setting->instrument);
    assure( !cpl_error_get_code(), return, "Could not write %s QC parameters",
            CORRECTION_MAP);
*/

        if (qc_zeropoint_err > 0.0) {
            fors_qc_write_qc_double(qc,
                                    qc_zeropoint,
                                    "QC.INSTRUMENT.ZEROPOINT",
                                    "mag",
                                    "Instrument zeropoint",
                                    setting->instrument);

            fors_qc_write_qc_double(qc,
                                    qc_zeropoint_err,
                                    "QC.INSTRUMENT.ZEROPOINT.ERROR",
                                    "mag",
                                    "Instrument zeropoint error",
                                    setting->instrument);
        }

        if (qc_extinction_err > 0.0 && summary == NULL) {
            fors_qc_write_qc_double(qc,
                                    qc_extinction,
                                    "QC.ATMOSPHERIC.EXTINCTION",
                                    "mag/airmass",
                                    "Atmospheric extinction",
                                    setting->instrument);

            fors_qc_write_qc_double(qc,
                                    qc_extinction_err,
                                    "QC.ATMOSPHERIC.EXTINCTION.ERROR",
                                    "mag/airmass",
                                    "Atmospheric extinction error",
                                    setting->instrument);
        }

        if (qc_colorterm_err > 0.0) {
            fors_qc_write_qc_double(qc,
                                    qc_colorterm,
                                    "QC.COLOR.CORRECTION",
                                    NULL,
                                    "Linear color correction term",
                                    setting->instrument);

            fors_qc_write_qc_double(qc,
                                    qc_colorterm_err,
                                    "QC.COLOR.CORRECTION.ERROR",
                                    NULL,
                                    "Linear color correction term error",
                                    setting->instrument);
        }

        fors_qc_end_group();

        /*
         * End write QCs
         */
    }
    passure(!cpl_error_get_code(), return);

    if (summary && phot_coeff) {
        cpl_table_erase_column(phot_coeff, "EXT");
        cpl_table_erase_column(phot_coeff, "DEXT");
        if (1 == cpl_table_get_ncol(phot_coeff)) {
            cpl_table_delete(phot_coeff);
            phot_coeff = NULL;
        }
    }

    if (phot_coeff) {
        fors_dfs_save_table(frames, phot_coeff, PHOT_COEFF_TABLE,
                            qc, parameters, fors_photometry_name,
                            cpl_frameset_get_first_const(master_flat_frame));
        cpl_propertylist_delete(qc); qc = NULL;
    }
    
    assure( !cpl_error_get_code(), return, "Saving %s failed",
            PHOT_COEFF_TABLE);

    if (summary) {
        if (fit_e == FORS_FIT_NCOEFF_PERFRAME) {
            tag = EXTINCTION_PER_FRAME;
        }
        if (fit_e == FORS_FIT_NCOEFF_PERNIGHT) {
            tag = EXTINCTION_PER_NIGHT;
        }
        fors_dfs_save_table(frames, summary, tag, NULL, parameters, 
                            fors_photometry_name,
                            cpl_frameset_get_first_const(master_flat_frame));
    }
    
    assure( !cpl_error_get_code(), return, "Saving %s failed", tag);

    if (degreef1 > 0) {
    
        fors_dfs_save_image_err(frames, correction, CORRECTION_MAP,
                            qc, parameters, fors_photometry_name, 
                            cpl_frameset_get_first_const(master_flat_frame));

    }

    assure( !cpl_error_get_code(), return, "Saving %s failed",
            CORRECTION_MAP);

    cpl_propertylist_delete(qc);
    
    /* Convert from magnitude to flux.
       F = 10^(-0.4 m)
    */
    if (degreef1 > 0) {
        cpl_msg_info(cpl_func, "Creating correction map (flux)");
    
        fors_image_multiply_scalar(correction, -0.4, -1);
        fors_image_exponential(correction, 10, -1);
    
        /* Normalize to median = 1 */
        fors_image_divide_scalar(correction, 
                                 fors_image_get_median(correction, NULL), -1.0);
    
        fors_dfs_save_image_err(frames, correction, CORRECTION_FACTOR,
                            NULL, parameters, fors_photometry_name, 
                            cpl_frameset_get_first_const(master_flat_frame));
    
        assure( !cpl_error_get_code(), return, "Saving %s failed",
                CORRECTION_FACTOR);
    }
    
    if (degreef1 > 0) {
        cpl_msg_info(cpl_func, "Creating corrected master flat");
        fors_image_multiply(master_flat, correction);
    
        fors_dfs_save_image_err(frames, master_flat, MASTER_FLAT_IMG,
                            NULL, parameters, fors_photometry_name, 
                            cpl_frameset_get_first_const(master_flat_frame));
        assure( !cpl_error_get_code(), return, "Saving %s failed",
                MASTER_FLAT_IMG);
    }
    
    cleanup;
    return;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    fors_std_star_delete(&std_star); \
    cpl_propertylist_delete(header); header = NULL; \
    cpl_table_delete(aligned_phot); aligned_phot = NULL; \
    fors_setting_delete(&setting_f); \
    fors_delete_star_lists(&obs, std_star_list); \
    fors_star_delete_but_standard(&obs_star); \
    fors_std_star_delete(&std_star); \
    \
    *n_frames = 0; \
} while (0)
/**
 * @brief   Read input data from aligned phot tables
 * @param   alphot_frames           Input frames
 * @param   setting                 Instrument setting
 * @param   get_atm_ext_id_function Function to create an identifier for the
 *                                  atmospheric extinction by frame header
 * @param   import_unknown          Flag whether to import also untrusted and
 *                                  non-standard stars
 * @param   n_frames                (Output) number of frames
 * @param   std_star_list           (Output) list of standard stars
 * @return set of observations
 * 
 * We match by (RA, DEC) and tolerance, not
 * by OBJECT string which is not unique across different catalogs.
 */
/*----------------------------------------------------------------------------*/
static entry_list *
fors_photometry_read_input(                 const cpl_frameset  *alphot_frames,
                                            const fors_setting  *setting,
                                            int (*get_atm_ext_id_function)(
                                                const cpl_propertylist *header),
                                            bool                import_unknown,
                                            int                 *n_frames,
                                            fors_std_star_list  **std_star_list,
                                            int                 filter)
{
    entry_list          *obs = NULL;
    fors_setting        *setting_f = NULL;
    fors_star           *obs_star = NULL;
    fors_std_star       *std_star = NULL;
    cpl_propertylist    *header = NULL;
    cpl_table           *aligned_phot = NULL;
    const cpl_frame     *f;
    int                 iframe,
                        inonstd = 0;
    cpl_errorstate      errstat = cpl_errorstate_get();
    
    /* init output pointers */
    cleanup;
    
    /* prepare */
    obs = entry_list_new();
    *std_star_list = fors_std_star_list_new();
    
    /*
     * Loop on all aligned photometric tables in input, and count the
     * found frames in iframe.
     */
    for (f = cpl_frameset_get_first_const(alphot_frames), iframe = 0;
         f != NULL;
         f = cpl_frameset_get_next_const(alphot_frames), iframe++)
    {
        const char  *filename;
        int         atm_ext_id = 0;
        double      airmass;
        
        filename = cpl_frame_get_filename(f);
        cpl_msg_info(cpl_func, "Loading %s", filename);
        cpl_msg_indent_more();

        aligned_phot = cpl_table_load(filename, 1, 1);
        assure(                             cpl_errorstate_is_equal(errstat),
                                            return NULL,
                                            "Could not load %s",
                                                filename);
/* %%% */
        if (filter && cpl_table_has_column(aligned_phot, "WEIGHT")) {
            cpl_table_and_selected_double(aligned_phot, 
                                          "WEIGHT", CPL_LESS_THAN, 1.0);
            cpl_table_and_selected_double(aligned_phot, 
                                          "WEIGHT", CPL_GREATER_THAN, -1.0);
            cpl_table_erase_selected(aligned_phot);
        }
/* %%% */

        header = cpl_propertylist_load(filename, 0);
        assure(                             cpl_errorstate_is_equal(errstat),
                                            return NULL,
                                            "Could not load %s header",
                                                filename);
        
        /* 
         * Load and verify setting for this frame 
         */
        fors_setting_verify(setting, f, &setting_f);
        
        airmass = cpl_propertylist_get_double(header, "AIRMASS");
        assure(                             cpl_errorstate_is_equal(errstat),
                                            return NULL,
                                            "%s: Could not read %s",
                                                filename, "AIRMASS");

        /* FIXME:
         * This whole section is for verifying the input table. The
         * structure of this table is probably defined in some other
         * place too... Likely this is a dependency between modules
         * that should be eliminated. Please check... (C.Izzo, 28.01.08)
         */
        struct {
            const char  *name;
            cpl_type    type;
            bool        optional;
        } col[] =
              {{"RA",           CPL_TYPE_DOUBLE, false},
               {"DEC",          CPL_TYPE_DOUBLE, false},
               {"X",            CPL_TYPE_DOUBLE, false},
               {"Y",            CPL_TYPE_DOUBLE, false},
               {"FWHM",         CPL_TYPE_DOUBLE, false},
               {"A",            CPL_TYPE_DOUBLE, false},
               {"B",            CPL_TYPE_DOUBLE, false},
               {"THETA",        CPL_TYPE_DOUBLE, false},
               {"INSTR_MAG",    CPL_TYPE_DOUBLE, false},
               {"DINSTR_MAG",   CPL_TYPE_DOUBLE, false},
               {"CAT_MAG",      CPL_TYPE_DOUBLE, false},
               {"DCAT_MAG",     CPL_TYPE_DOUBLE, false},
               {"MAG",          CPL_TYPE_DOUBLE, false},
               {"DMAG",         CPL_TYPE_DOUBLE, false},
               {"COLOR",        CPL_TYPE_DOUBLE, false},
               {"DCOLOR",       CPL_TYPE_DOUBLE, true}, /* miss in old data */
               {"COV_CATM_COL", CPL_TYPE_DOUBLE, true}, /* miss in old data */
               {"CLASS_STAR",   CPL_TYPE_DOUBLE, false},
               {"USE_CAT",      CPL_TYPE_INT,    false},
               {"OBJECT",       CPL_TYPE_STRING, false}};
        {
            unsigned i = 0;
            for (i = 0; i < sizeof(col) / sizeof(*col); i++)
            {
                bool    exists;
                exists = cpl_table_has_column(aligned_phot, col[i].name);
                cassure(exists || col[i].optional,
                        CPL_ERROR_DATA_NOT_FOUND,
                        return NULL,
                        "%s: Missing column %s", filename, col[i].name);
                cassure((!exists) ||
                        cpl_table_get_column_type(aligned_phot, col[i].name) 
                            == col[i].type,
                        CPL_ERROR_INVALID_TYPE,
                        return NULL,
                        "%s: column %s: Type is %s, %s expected ", 
                        filename,
                        col[i].name,
                        fors_type_get_string(
                            cpl_table_get_column_type(aligned_phot, 
                                                      col[i].name)
                        ),
                        fors_type_get_string(col[i].type));
            }
        } /* Table check done */
        
        if (get_atm_ext_id_function != NULL)
        {
            atm_ext_id = get_atm_ext_id_function(header);
            assure(                         cpl_errorstate_is_equal(errstat),
                                            return NULL,
                                            "Getting atmospheric extinction "
                                            "identifier failed.");
        }
        
        /* 
         * Get IDed stars in this table: 
         */
        int i;
        for (i = 0; i < cpl_table_get_nrow(aligned_phot); i++)
        {
            obs_star = fors_star_new_from_table(
                                            aligned_phot,
                                            i,
                                            "X", "Y",
                                            "FWHM",
                                            "A", "B",
                                            "THETA", 
                                            "INSTR_MAG", "DINSTR_MAG",
                                            "CLASS_STAR");
            std_star = fors_std_star_new_from_table(
                                            aligned_phot,
                                            i,
                                            "RA", "DEC",
                                            "MAG", "DMAG",
                                            "CAT_MAG", "DCAT_MAG",
                                            "COLOR", NULL,   /* DCOLOR */
                                            NULL,   /* COV_CATM_COL */
                                            NULL, NULL,
                                            "OBJECT");
            /* compatibility with old fors_zeropoint products */
            if (cpl_table_has_column(aligned_phot, "DCOLOR")
                && cpl_table_has_column(aligned_phot, "COV_CATM_COL"))
            {
                std_star->dcolor = cpl_table_get(
                                            aligned_phot,
                                            "DCOLOR",
                                            i,
                                            NULL);
                std_star->cov_catm_color = cpl_table_get(
                                            aligned_phot,
                                            "COV_CATM_COL",
                                            i,
                                            NULL);
            }
            /*
             * The star and the standard star are the same star. 
             * The star is detector-oriented, the standard star
             * is its identification and includes physical propeties.
             * Here the information is linked together, and the
             * standard star is "owned" by the star object - so
             * it should not be destroyed.  (C.Izzo, 28.01.08)
             */
            obs_star->id = std_star;
            
            /* Use catalog magnitude xor fit the magnitude: */
            std_star->trusted = (0 != cpl_table_get_int(
                                            aligned_phot, "USE_CAT", i, NULL));
            
            assure(                         cpl_errorstate_is_equal(errstat)
                                            && obs_star != NULL
                                            && std_star != NULL,
                                            return NULL,
                                            "Reading from aligned photometry "
                                            "table failed.");
            
            cassure(                        obs_star->dmagnitude > 0,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return NULL, 
                                            "Non-positive magnitude error: "
                                            "%f mag at row %d",
                                            obs_star->dmagnitude,
                                            i+1);
            
            /*fors_star_print(CPL_MSG_DEBUG, obs_star);
            fors_std_star_print(CPL_MSG_DEBUG, std_star);*/
            
            if (! fors_extract_check_sex_star(obs_star, NULL))
            {
                
                cpl_msg_warning(cpl_func,   "Rejecting object no. %d from "
                                            "frame %d, "
                                            "which should have been caught "
                                            "by recent fors_zeropoint recipe. "
                                            "Consider reprocessing input data.",
                                            i+1, iframe+1);
                fors_std_star_delete(&std_star);
            }
            else if (std_star->name != NULL && (std_star->name)[0] != '\0')
            {
                /* Object with name is standard star */
                /* Avoid duplicate standard star list entries. */
                std_star = fors_photometry_read_input_listinsert_star_if_new(
                                            *std_star_list,
                                            std_star,
                                            arcsec_tol);
            }
            else if (import_unknown)    /* import non-std stars */
            {
                fors_std_star   *s_in_list;
                
                std_star->trusted = false;  /* don't trust ignoring table flag*/
                
                s_in_list = fors_photometry_read_input_listinsert_star_if_new(
                                            *std_star_list,
                                            std_star,
                                            arcsec_tol);
                
                if (s_in_list == std_star) /* made it into list */
                {
                    char            name[16];
                    sprintf(name, "non-std-%d", inonstd);
                    fors_std_star_set_name(std_star, name);
                    inonstd++;
                }
                
                std_star = s_in_list;
            }
            else
            {
                fors_std_star_delete(&std_star);
            }
            
            if (std_star != NULL)
            {
                entry   *e;
                obs_star->id = std_star;
                /* cannot identify std star id yet, because the used list
                 * object totally scrambles the order (for whatever reason) */
                e = entry_new(              iframe,
                                            -1,
                                            airmass, 
                                            setting_f->average_gain,
                                            setting_f->exposure_time,
                                            atm_ext_id,
                                            obs_star);
                entry_list_insert(      obs, e);
            }
            else
            {
                fors_star_delete_but_standard(&obs_star);
            }

        }/* for each star */

        cpl_propertylist_delete(header); header = NULL;
        cpl_table_delete(aligned_phot); aligned_phot = NULL;
        fors_setting_delete(&setting_f);

        cpl_msg_indent_less();
    }/* For each table */

    *n_frames = iframe;
    
    
    /* The used list object is actually fed in reverse order (because there
     * is only the function list_insert, but not list_append). But we would
     * like to access the elements in the same order as we imported them, so
     * the fix was the invention of the function list_reverse().
     * hlorch, 17.02.2009 */
    entry_list_reverse(obs);
    fors_std_star_list_reverse(*std_star_list);
    
    /* determine the observation's object id */
    {
        fors_std_star   *ref;
        entry           *e;
        
        for (e = entry_list_first(obs); e != NULL; e = entry_list_next(obs))
        {
            int i;
            for (   ref = fors_std_star_list_first(*std_star_list), i = 0;
                    ref != NULL;
                    ref = fors_std_star_list_next(*std_star_list), i++)
            {
                if (e->star->id == ref)
                {
                    e->star_index = i;
                    break;
                }
            }
        }
    }

    cassure(                                entry_list_size(obs) > 0,
                                            CPL_ERROR_DATA_NOT_FOUND,
                                            return NULL,
                                            "No stars found");

    return obs;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/**
 * @brief   Insert a std star into a list, if the closest existing distance > maxd_arcsec.
 * @param   std_list    Standard star list
 * @param   std         Standard star to test
 * @param   mind_arcsec Minimum distance in arc seconds
 * @return  Pointer either to input star or to list star
 * 
 * In the case that the @a std star is already found in the standard star list,
 * the input @a std star object is deleted.
 * 
 * In the case of error, the input star is not deleted and its pointer is
 * returned.
 */
/*----------------------------------------------------------------------------*/
static fors_std_star*
fors_photometry_read_input_listinsert_star_if_new(
                                            fors_std_star_list  *std_list,
                                            fors_std_star       *std,
                                            double              mind_arcsec)
{
    cpl_errorstate  errstat = cpl_errorstate_get();
    bool            found = false;
    
    cassure_automsg(                        std_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return std);
    cassure_automsg(                        std != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return std);
    cassure_automsg(                        mind_arcsec > 0,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return std);
    
    if (fors_std_star_list_size(std_list) > 0)
    {
        fors_std_star   *nearest;
        double          dist_arcsec;
        /* Only if the nearest standard star is farther 
         * away than 5 arcsecs, insert this standard star 
         * in the standard star list. */
        nearest = fors_std_star_list_kth_val(
                                            std_list, 1,
                                            (fors_std_star_list_func_eval)
                                            fors_std_star_dist_arcsec,
                                            std);
        passure(cpl_errorstate_is_equal(errstat), return std);
        
        dist_arcsec = fors_std_star_dist_arcsec(nearest, std);
        passure(cpl_errorstate_is_equal(errstat), return std);
        
        cpl_msg_debug(                      cpl_func,
                                            "dist = %f arcseconds",
                                            dist_arcsec);

        if (dist_arcsec < mind_arcsec)
        {
            /* trust a star only if it's always trusted */
            nearest->trusted &= std->trusted;
            /* delete the new star and link to the old one */
            fors_std_star_delete(&std);
            std = nearest;
            found = true;
        }
    }
    
    if (!found)
    {
        fors_std_star_list_insert(std_list, std);
    }
    
    return std;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup
/*
 * @brief   Adjust @em trusted flags.
 * @param   stdl            Standard star list
 * @param   el              Observation entry list
 * @param   override_fit_m  Flag whether to fit all catalog magnitudes
 * @param   n_mag_fits      (Output) number of magnitudes to fit
 * @return  CPL error code
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
fors_photometry_adjust_fit_mag_flags(       fors_std_star_list  *stdl,
                                            entry_list          *obsl,
                                            bool                override_fit_m,
                                            int                 *n_mag_fits)
{
    fors_std_star   *std;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    cassure_automsg(                        stdl != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        obsl != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        n_mag_fits != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    
    *n_mag_fits = 0;
    
    /* If (override_fit_m), then fit all magnitudes.
     * The intention is to fit in a best way the f polynomial. Just one
     * star is necessary to fix the offset, so just don't fit the magnitude
     * of the first one. */
    if (override_fit_m)
    {
        std = fors_std_star_list_first(stdl);
        /* find the first trusted object */
        while (std != NULL && std->trusted == false)
        {
            std = fors_std_star_list_next(stdl);
            (*n_mag_fits)++;
        }
        /* keep this one */
        if (std != NULL)
            std = fors_std_star_list_next(stdl);
        /* and set the rest to non-trusted */
        for ( ; std != NULL; std = fors_std_star_list_next(stdl))
        {
            std->trusted = false;
            (*n_mag_fits)++;
        }
    }
    else
    {
        for (   std = fors_std_star_list_first(stdl);
                std != NULL;
                std = fors_std_star_list_next(stdl))
        {
            if(! std->trusted)
                (*n_mag_fits)++;
        }
    }
    
    return (cpl_errorstate_is_equal(errstat) ?
            CPL_ERROR_NONE :
            cpl_error_get_code());
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_array_delete(n_obs_per_std); n_obs_per_std = NULL; \
    fors_std_star_list_delete(&std_list_copy, NULL); \
    entry_list_delete(&obs_list_copy, NULL); \
} while (0)
/**
 * @brief   Remove all standard stars which are fitted but were observed only once.
 * @param   std_list    Standard star list (modified)
 * @param   obs_list    Observation list (modified)
 * @param   n_mag_fits  (Output) number of remaining untrusted std stars
 * @return  CPL error code
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
fors_photometry_remove_unnecessary(         fors_std_star_list  *std_list,
                                            entry_list          *obs_list,
                                            int                 *n_mag_fits)
{
    cpl_array           *n_obs_per_std = NULL;
    fors_std_star_list  *std_list_copy = NULL;
    entry_list          *obs_list_copy = NULL;
    fors_std_star       *std;
    entry               *obs;
    int                 n_std_stars,
                        n_removed = 0,
                        n;
    cpl_errorstate      errstat = cpl_errorstate_get();
    
    cassure_automsg(                        std_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        obs_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        n_mag_fits != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    
    *n_mag_fits = 0;
    
    n_std_stars = fors_std_star_list_size(std_list);
    
    n_obs_per_std = fors_photometry_count_observations(std_list, obs_list);
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    /* We are not so sure what happens if we iterate the FORS list and at the
     * same time remove entries, so copy the list to temporarily keep the
     * pointers. */
    std_list_copy = fors_std_star_list_duplicate(std_list, NULL);
    obs_list_copy = entry_list_duplicate(obs_list, NULL);
    
    /* first remove all unnecessary observation entries */
    for (   obs = entry_list_first(obs_list_copy);
            obs != NULL;
            obs = entry_list_next(obs_list_copy))
    {
        int     n_obs;
        bool    remove = false;
        
        remove |= (obs->star_index < 0 || obs->star_index >= n_std_stars);
        
        n_obs = cpl_array_get_int(n_obs_per_std, obs->star_index, NULL);
        assure(                             cpl_errorstate_is_equal(errstat),
                                            return cpl_error_get_code(),
                                            NULL);
        remove |= (n_obs < 2 && !obs->star->id->trusted);
        
        if (remove)
        {
            entry_list_remove(obs_list, obs);
            entry_delete_but_standard(&obs);
        }
    }
    
    for (   std = fors_std_star_list_first(std_list_copy), n = 0;
            std != NULL;
            std = fors_std_star_list_next(std_list_copy), n++)
    {
        int     n_obs;
        
        n_obs = cpl_array_get_int(n_obs_per_std, n, NULL);
        assure(                             cpl_errorstate_is_equal(errstat),
                                            return cpl_error_get_code(),
                                            NULL);
        
        if (n_obs < 2 && !std->trusted)
        {
            fors_std_star_list_remove(std_list, std);
            fors_std_star_delete(&std);
            n_removed++;
        }
        else if(!std->trusted)
        {
            (*n_mag_fits)++;
        }
    }
    
    cleanup;    /* don't need that anymore */
    
    /* Set the new star indices */
    for (   obs = entry_list_first(obs_list);
            obs != NULL;
            obs = entry_list_next(obs_list))
    {
        for (   std = fors_std_star_list_first(std_list), n = 0;
                std != NULL;
                std = fors_std_star_list_next(std_list), n++)
        {
            if (obs->star->id == std)
            {
                obs->star_index = n;
                break;
            }
        }
    }
    
    if (n_removed > 0)
        cpl_msg_info(   cpl_func,
                        "Discarded %d untrusted/fitted objects which were "
                        "observed only once (and therefore don't contribute).",
                        n_removed);
    
    cleanup;
    return (cpl_errorstate_is_equal(errstat) ?
            CPL_ERROR_NONE :
            cpl_error_get_code());
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    cpl_array_unwrap(n_obs_a); n_obs_a = NULL; \
    cpl_free(n_obs); n_obs = NULL; \
} while (0)
/**
 * @brief Count the number of observations of each standard star.
 * @param   std_list    Standard star list
 * @param   obs_list    Observation list
 * @return  Array of type CPL_TYPE_INT and the same size as @a std_list,
 *          NULL in the case of error.
 */
/*----------------------------------------------------------------------------*/
static cpl_array*
fors_photometry_count_observations(         fors_std_star_list  *std_list,
                                            entry_list          *obs_list)
{
    int         *n_obs = NULL;
    cpl_array   *n_obs_a = NULL;
    entry       *e;
    int         n_std_stars;
    
    cassure_automsg(                        std_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return n_obs_a);
    cassure_automsg(                        obs_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return n_obs_a);
    
    n_std_stars = fors_std_star_list_size(std_list);
    n_obs = cpl_calloc(n_std_stars, sizeof(*n_obs));
    
    for (   e = entry_list_first(obs_list);
            e != NULL;
            e = entry_list_next(obs_list))
    {
        ppassure(                           e->star_index >= 0
                                            && e->star_index < n_std_stars,
                                            CPL_ERROR_UNSPECIFIED,
                                            return n_obs_a);
        n_obs[e->star_index]++;
    }
    
    n_obs_a = cpl_array_wrap_int(n_obs, n_std_stars);
    return n_obs_a;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    fors_matrix_null(&lhs); \
    if (n_fit_e_cols != NULL) \
        *n_fit_e_cols = 0; \
} while (0)
/**
 * @brief   Build left hand side matrix equation part
 * @param   obs_list        List of star observations
 * @param   std_list        List of std. stars
 * @param   fit_z           fit zeropoint?
 * @param   fit_c           fit color coeff.?
 * @param   n_fit_e_cols    (Optional) output number of atmospheric extinctions
 * @return  Matrix
 * 
 * @par Details:
 * - This function returns NULL, if the number of parameters is 0 (i.e. if
 *   nothing is to be fitted here), without setting an error.
 * 
 * @par Atmospheric Extinctions:
 * - This function creates as many columns for atmospheric extinctions
 *   as there are indices for atmospheric extinction found in @a obs_list
 *   (the number can be zero).
 * - The atmospheric extinction indices (if one should be valid) must count from
 *   zero and must not lack numbers inbetween (unchecked).
 * - Indices < 0 do not contribute, @a obs_list entries with an index < 0
 *   will get a row of zeros in these columns.
 * - The number of created columns for the individual atmospheric extinctions
 *   is returned via @a n_fit_e_cols.
 */
/*----------------------------------------------------------------------------*/
static cpl_matrix*
build_equations_lhs_matrix_from_parameters( const entry_list    *obs_list,
                                            const fors_std_star_list  *std_list,
                                            bool                fit_z,
                                            bool                fit_c,
                                            int                 *n_fit_e_cols)
{
    int             n_std_stars,
                    n_obs,
                    n_col,
                    n_fit_std_mag = 0,
                    n_frames,
                    n_atm_ext,  /* nr of atm. extinction coefficients */
                    row;
    const entry     *e;
    const fors_std_star
                    *std;
    cpl_matrix      *lhs = NULL;
    bool            printed = false;
    
    /* free output pointers */
    cleanup;
    
    /* check input */
    assure(!cpl_error_get_code(), return lhs, "Previous error caught.");
    
    ppassure(                               obs_list != NULL
                                            && std_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return lhs);
    
    n_std_stars = fors_std_star_list_size(std_list);
    n_obs = entry_list_size(obs_list);
    
    cassure(                                n_std_stars > 0 && n_obs > 0,
                                            CPL_ERROR_DATA_NOT_FOUND,
                                            return lhs,
                                            "Empty input list");
    
    /* prepare */
    n_obs = entry_list_size(obs_list);
    for (   std = fors_std_star_list_first_const(std_list);
            std != NULL;
            std = fors_std_star_list_next_const(std_list))
    {
        n_fit_std_mag += !(std->trusted);
    }
    
    n_frames = 0;
    n_atm_ext = 0;
    for (   e = entry_list_first_const(obs_list);
            e != NULL;
            e = entry_list_next_const(obs_list))
    {
        /* assume indices counting from 0 */
        if (e->frame_index + 1 > n_frames)
            n_frames = e->frame_index + 1;
        if (e->atm_ext_index + 1 > n_atm_ext)
            n_atm_ext = e->atm_ext_index + 1;
    }
    if (n_atm_ext < 0) n_atm_ext = 0;
    passure(!cpl_error_get_code(), return lhs);
    
    n_col = n_fit_std_mag
            + ((fit_z) ? 1 : 0)
            + n_atm_ext
            + ((fit_c) ? 1 : 0);
    
    if (n_col == 0) /* if nothing to be fitted here */
    {
        cleanup;
        return lhs; /* NULL */
    }
    
    lhs = cpl_matrix_new(n_obs, n_col);
    passure(!cpl_error_get_code(), return lhs);
    
    /* start */
    /* FIXME: FAP: insert visual comments here */
    for (e = entry_list_first_const(obs_list), row = 0;
         e != NULL;
         e = entry_list_next_const(obs_list), row++) 
    {
        int col = 0,
            k;
        
        /* Star not identified, should not happen */
        ppassure(                           e->star_index >= 0,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return lhs);
        
        if (n_fit_std_mag > 0) /* one column per std. star's magnitude to fit */
        {
            for (   std = fors_std_star_list_first_const(std_list), k = 0;
                    std != NULL;
                    std = fors_std_star_list_next_const(std_list), k++)
            {
                if (!(std->trusted))
                {
                    if (!printed)
                        cpl_msg_debug(      cpl_func,
                                            "Creating column for mag(M%d)",
                                            k);
                    if (e->star->id == std)
                        cpl_matrix_set(lhs, row, col, 1);
                    col++;
                }
            }
        }
        
        if (fit_z)
        {
            if (!printed)
                cpl_msg_debug(cpl_func, "Creating column for Z");
            cpl_matrix_set(lhs, row, col++, -1);
        }
        
        if (n_atm_ext > 0)
        {
            for (k = 0; k < n_atm_ext; k++)
            {
                if (!printed)
                    cpl_msg_debug(cpl_func, "Creating column for E_%d", k);
                double val = (k == e->atm_ext_index) ? e->airmass : 0;
                cpl_matrix_set(lhs, row, col++, val);
            }
        }

        if (fit_c)  /* fit color coeff */
        {
            double  c;
            if (!printed)
                cpl_msg_debug(cpl_func,     "Creating column for color "
                                            "correction term");
            c = e->star->id->color;
            /* if (fit_mag), then fit observed magnitude, not corrected by
             * catalogue color, or in other words: set std.star elements
             * to zero */
            if (!(e->star->id->trusted))
                c = 0;
            cpl_matrix_set(lhs, row, col++, c);
        }
        printed = true;
    }
    passure(!cpl_error_get_code(), return lhs);
    
    if (n_fit_e_cols != NULL)
        *n_fit_e_cols = n_atm_ext;
    
    return lhs;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    fors_matrix_null(&mat); \
    cpl_array_delete(Apowers); Apowers = NULL; \
} while (0)
/**
 * @brief   Build polynomial fitting matrix with coefficients represented in columns.
 * @param   obs_list    Observation list
 * @param   poly        Polynomial definition
 * @param   pname       (Optional) name of the polynomial for debug messages
 * @param   powerfunc   Function to evaluate a combination of powers
 * @return  Matrix, NULL if the polynomial contains no non-zero coefficients
 * 
 * @par Principle:
 * - For every observation in @a obs_list, a matrix row is created,
 * - for every non-zero coefficient in @a poly, a matrix column is created,
 * - looping through the existing coefficients (first dimension is fastest
 *   index), the fitting value is inserted into the respective matrix element
 *   calling @a powerfunc with the coefficient'c combination of powers,
 * - where the powers are provided as a cpl_array whose size equals the
 *   number of dimensions of @a poly,
 * - if @a poly has no non-zero coefficients, then NULL is returned.
 */
/*----------------------------------------------------------------------------*/
static cpl_matrix*
build_equations_lhs_matrix_from_poly(       const entry_list        *obs_list,
                                            const cpl_polynomial    *poly,
                                            const char              *pname,
                                            double (*powerfunc)(
                                                            const entry*,
                                                            const cpl_array*))
{
    int             n_obs,
                    n_coeff,
                    n_dims,
                    row;
    cpl_matrix      *mat = NULL;
    cpl_array       *Apowers = NULL;
    int             *ipowers;
    cpl_size        *ipowers_size;
    const entry     *e;
    cpl_error_code  errc;
    bool            printed = false;
    int             i;
    
    assure(!(errc=cpl_error_get_code()), return NULL, "Previous error caught.");
    
    /* check input */
    ppassure(                               poly != NULL && obs_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return NULL);
    
    /* init */
    n_obs = entry_list_size(obs_list);
    n_coeff = fors_polynomial_count_coeff(poly);
    passure(!cpl_error_get_code(), return NULL);
    
    if (n_coeff == 0)
        return NULL;
    
    mat = cpl_matrix_new(n_obs, n_coeff);
    
    /* start */
    n_dims = cpl_polynomial_get_dimension(poly);
    Apowers = cpl_array_new(n_dims, CPL_TYPE_INT);
    cpl_array_fill_window_int(Apowers, 0, n_dims, 0);
    ipowers = cpl_array_get_data_int(Apowers);
    passure(!cpl_error_get_code(), return NULL);
    
    //This is a workaround until cpl_array supports CPL_SIZE type elements. 
    ipowers_size = cpl_malloc(n_dims*sizeof(ipowers_size));
    for (i=0; i<n_dims; i++)
        ipowers_size[i] = ipowers[i];
    
    for (e = entry_list_first_const(obs_list), row = 0;
         e != NULL;
         e = entry_list_next_const(obs_list), row++) 
    {
        int     col = 0;
        bool    overflow;
        
        overflow = fors_polynomial_powers_find_first_coeff(poly, ipowers_size);
        while (!overflow)
        {
            if (!printed)
            {
                char *cn = fors_polynomial_sprint_coeff(poly, ipowers_size, pname);
                if (cn != NULL)
                {
                    cpl_msg_debug(cpl_func, "Creating column for %s", cn);
                    cpl_free(cn);
                }
            }
            cpl_matrix_set(mat, row, col++, (*powerfunc)(e, Apowers));
            passure(!cpl_error_get_code(), return NULL);
            
            overflow = fors_polynomial_powers_find_next_coeff(poly, ipowers_size);
        }
        
        printed = true;
    }
    
    cpl_array_delete(Apowers);
    
    return mat;
}

/*----------------------------------------------------------------------------*/
#undef cleanup
#define cleanup \
do { \
    fors_matrix_null(&rhs_input_cov); \
    fors_matrix_null(&rhs_jacobian); \
    fors_matrix_null(&rhs_input); \
    fors_matrix_null(&rhs_jacobian_T); \
    fors_matrix_null(&tmp_matrix); \
    fors_matrix_null(rhs); \
    fors_matrix_null(rhs_cov); \
} while (0)
/**
 * @brief   Build right hand side matrix equation part
 * @param   obs_list        List of star observations
 * @param   std_list        List of std. stars
 * @param   fit_z           fit zeropoint?
 * @param   fit_c           fit color coeff.?
 * @param   fit_e           fit extinction coeff.?
 * @param   color_coeff     color coefficient
 * @param   dcolor_coeff    color coefficient error
 * @param   ext_coeff       extinction coefficient
 * @param   dext_coeff      ext_coeff error
 * @param   rhs             (output) right hand side
 * @param   rhs_cov         (output) covariance of rhs
 * @return  CPL error code
 */
/*----------------------------------------------------------------------------*/
static cpl_error_code
build_equations_rhs_cov(                    const entry_list    *obs_list,
                                            const fors_std_star_list  *std_list,
                                            bool                fit_z,
                                            bool                fit_c,
                                            bool                fit_e,
                                            double              color_coeff,
                                            double              dcolor_coeff,
                                            double              ext_coeff,
                                            double              dext_coeff,
                                            double              zpoint,
                                            double              dzpoint,
                                            cpl_matrix          **rhs,
                                            cpl_matrix          **rhs_cov)
{
    /* This function computes the following
     * (with i = index of referenced std. star):
     * 
     * rhs_obs = m_obs (instrumental magnitude)
     *           - (!fit_mag_i)     ? cat_mag_i : 0
     *           + (!fit_c)         ? color_i * color_coeff : 0
     *           - (!fit_e)         ? airmass_f * ext_coeff : 0
     *           + (!fit_z)         ? zpoint : 0
     *           - magscale(gain)
     *           - magscale(exposure_time);
     * 
     * It does it by generating 3 matrices: the inputs matrix, a Jacobian, and
     * an inputs covariance matrix. Using these, the rhs and its covariance
     * matrix are computed.
     */
    cpl_matrix      *rhs_input_cov = NULL,
                    *rhs_jacobian = NULL,
                    *rhs_input = NULL,
                    *rhs_jacobian_T = NULL,
                    *tmp_matrix = NULL;
    int             n_std_stars,
                    n_obs,
                    n_col,
                    r,
                    c;
    const fors_std_star
                    *std;
    const entry     *obs;
    bool            printed_debug = false;
    cpl_errorstate  errstat = cpl_errorstate_get();
    
    /* free output pointers */
    cleanup;
    
    /* check input */
    cassure_automsg(                        obs_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        std_list != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        rhs != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    cassure_automsg(                        rhs_cov != NULL,
                                            CPL_ERROR_NULL_INPUT,
                                            return cpl_error_get_code());
    
    n_std_stars = fors_std_star_list_size(std_list);
    n_obs = entry_list_size(obs_list);
    
    cassure(                                n_std_stars > 0 && n_obs > 0,
                                            CPL_ERROR_DATA_NOT_FOUND,
                                            return cpl_error_get_code(),
                                            "Empty input list");
    
    /* start */
    n_col = n_std_stars*2 + 3;
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* build the following input covariance matrix:
     * 
     * v_mag_0  cov_mc_0  0        0           0            0          0
     * cov_mc_0 v_color_0 0        0           0            0          0
     * 0        0         v_mag_1  cov_mc_1    0            0          0
     * 0        0         cov_mc_1 v_color_1   0            0          0
     *                                      ...
     * 0        0         0        0           v_color_coef 0          0
     * 0        0         0        0           0            v_ext_coef 0
     * 0        0         0        0           0            0          v_zpoint
     */
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    rhs_input_cov = cpl_matrix_new(n_col, n_col);
    for (   std = fors_std_star_list_first_const(std_list), c = 0;
            std != NULL;
            std = fors_std_star_list_next_const(std_list), c += 2)
    {
        double  dcatm = std->dcat_magnitude,
                dcolor = std->dcolor,
                cov_catmag_color = std->cov_catm_color;
        
        /* To new maintainers: first understand the rest of the
         * function without the following if-statement. */
        if (!(dcolor > 0) || isnan(cov_catmag_color))
        {
            /* If we have old fors_zeropoint input data, i.e.
             * if dcolor and cov_catmag_color are not set, then:
             * 
             * use the mag entry in the covariance matrix and in the rhs input
             * vector:
             * - depently on fit_c, use the catalogue magnitude or the
             *   color corrected magnitude, and
             * - set the color +- dcolor entry to 0 +- 0.
             * 
             * The color corrected magnitude, computed by the old
             * fors_zeropoint recipe, included the correlation between
             * magnitude and color index, using the color correction term
             * from the then used photometric table.
             */
            cov_catmag_color = 0;
            dcolor = 0;
            if (std->trusted) /* !(fit magnitude), not really necessary
                                                   because Jacobian takes care
                                                   of (!(std->trusted)) */
            {
                if (!fit_c)
                {
                    cassure(                dcatm > 0,
                                            CPL_ERROR_ILLEGAL_INPUT,
                                            return cpl_error_get_code(),
                                            "Could not determine color "
                                            "corrected magnitude with error "
                                            "estimate of object %s",
                                            (std->name != NULL) ?
                                                std->name : "unknown");
                    dcatm = std->dmagnitude;    /* color corrected mag */
                    if (!printed_debug)
                    {
                        cpl_msg_debug(      cpl_func,
                                            "Having old fors_zeropoint data. "
                                            "Using color corrected magnitudes "
                                            "instead of catalogue magnitude "
                                            "and color separately.");
                        printed_debug = true;
                    }
                }
            }
            /* else fit the observed magnitude, i.e. not correcting by
             * the catalogue color (see Jacobian), so don't care about
             * missing color entries */
        }
        
        cpl_matrix_set(rhs_input_cov, c, c, dcatm*dcatm);
        cpl_matrix_set(rhs_input_cov, c+1, c+1, dcolor*dcolor);
        cpl_matrix_set(rhs_input_cov, c, c+1, cov_catmag_color);
        cpl_matrix_set(rhs_input_cov, c+1, c, cov_catmag_color);
    }
    cpl_matrix_set(rhs_input_cov, c, c, dcolor_coeff*dcolor_coeff);
    cpl_matrix_set(rhs_input_cov, c+1, c+1, dext_coeff*dext_coeff);
    cpl_matrix_set(rhs_input_cov, c+1, c+1, dzpoint*dzpoint);
    
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* build the following Jacobi matrix
     * with: i = index of referenced std. star,
     *       f = index of frame
     *       A = airmass,
     *       G = color correction term
     *       C = color
     * 
     * ... (!fit_mag_i)*1 -(!fit_c)*G ... -(!fit_c)*C_i (!fit_e)*A_f -(!fit_z)*1
     *         . 
     *         .
     *         .
     * 
     * and multiply by (-1) to subtract the input.
     * fit_c appears twice with the coefficients required to compute the
     * rhs covariance matrix.
     * In principle, the airmass index could also be the index of the
     * measurement/observation of the star, since it is taken from the obs
     * object.
     */
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    rhs_jacobian = cpl_matrix_new(n_obs, n_col);
    for (   obs = entry_list_first_const(obs_list), r = 0;
            obs != NULL;
            obs = entry_list_next_const(obs_list), r++)
    {
        bool    fit_mag,
                compensate_color;
        
        c = obs->star_index * 2;
        fit_mag = !(obs->star->id->trusted);
        /* if (fit_mag), then fit observed magnitude, not corrected by
         * catalogue color */
        compensate_color = (!fit_c) && (!fit_mag);
        
        cpl_matrix_set(rhs_jacobian, r, c,  -(!fit_mag)*1.0);
        cpl_matrix_set(rhs_jacobian, r, c+1, +(compensate_color)*color_coeff);
        
        cpl_matrix_set(rhs_jacobian, r, n_col-3, + (compensate_color)
                                                   * obs->star->id->color);
        cpl_matrix_set(rhs_jacobian, r, n_col-2, -(!fit_e)*obs->airmass);
        cpl_matrix_set(rhs_jacobian, r, n_col-1, +(!fit_z)*1.0);
    }
    
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* Prepare the rhs input vector:
     * 
     * [...  cat_mag_i  color_i  ...  0  ext_coef  zpoint]^T
     * 
     * Here, the term C*G shall only be used once, so set the other occurrence
     * to 0. 
     */
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    rhs_input = cpl_matrix_new(n_col, 1);
    for (   std = fors_std_star_list_first_const(std_list), r = 0;
            std != NULL;
            std = fors_std_star_list_next_const(std_list), r += 2)
    {
        double  catm = std->cat_magnitude,
                color = std->color;
        
        /* To new maintainers: first understand the rest of the
         * function without the following if-statement. */
        if (!(std->dcolor > 0) || isnan(std->cov_catm_color))
        {
            /* see above */
            color = 0;
            if (std->trusted) /* !(fit magnitude) */
            {
                if (!fit_c)
                    catm = std->magnitude;      /* color corrected mag */
            }
        }
        
        cpl_matrix_set(rhs_input, r, 0, catm);
        cpl_matrix_set(rhs_input, r+1, 0, color);
    }
    /* we already have color_i*color_coeff
     *cpl_matrix_set(rhs_input, r, 0, 0);*/
    cpl_matrix_set(rhs_input, r+1, 0, ext_coeff);
    cpl_matrix_set(rhs_input, r+2, 0, zpoint);
    
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* ...and compute the results... */
    *rhs = cpl_matrix_product_create(rhs_jacobian, rhs_input);
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    rhs_jacobian_T = cpl_matrix_transpose_create(rhs_jacobian);
    tmp_matrix = cpl_matrix_product_create(rhs_input_cov, rhs_jacobian_T);
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    *rhs_cov = cpl_matrix_product_create(rhs_jacobian, tmp_matrix);
    passure(cpl_errorstate_is_equal(errstat), return cpl_error_get_code());
    
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    /* add the missing contributions:
     * 1. rhs    : instrumental magnitude, subtract gain and exptime
     * 2. rhs_cov: variance of instrumental magnitude
     */
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    for (   obs = entry_list_first_const(obs_list), r = 0;
            obs != NULL;
            obs = entry_list_next_const(obs_list), r++)
    {
        cpl_matrix_set(*rhs, r, 0,      cpl_matrix_get(*rhs, r, 0)
                                        + obs->star->magnitude
                                        + 2.5*log10(obs->gain)
                                        + 2.5*log10(obs->exptime));
        cpl_matrix_set(*rhs_cov, r, r,  cpl_matrix_get(*rhs_cov, r, r)
                                        + obs->star->dmagnitude
                                          * obs->star->dmagnitude);
    }
    
    fors_matrix_null(&rhs_input_cov);
    fors_matrix_null(&rhs_jacobian);
    fors_matrix_null(&rhs_input);
    fors_matrix_null(&rhs_jacobian_T);
    fors_matrix_null(&tmp_matrix);
    
    return (    cpl_errorstate_is_equal(errstat) ?
                CPL_ERROR_NONE :
                cpl_error_get_code());
}


/**@}*/
