/*
 * This file is part of the ESO SINFONI Pipeline
 * Copyright (C) 2004,2005 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA
 */
/*---------------------------------------------------------------------------

 File name     :    fit_curve.c
 Author         :    N. Devillard
 Created on    :    July 1998
 Description    :    1d and 2d fit related routines

 ---------------------------------------------------------------------------*/
/*
 $Id: sinfo_fit_curve.c,v 1.4 2012-03-02 08:42:20 amodigli Exp $
 $Author: amodigli $
 $Date: 2012-03-02 08:42:20 $
 $Revision: 1.4 $
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
/*---------------------------------------------------------------------------
 Includes
 ---------------------------------------------------------------------------*/
#include <math.h>
#include "sinfo_fit_curve.h"
#include "sinfo_ipow.h"
/**@{*/
/**
 * @addtogroup sinfo_fit Fit functions
 *
 * TBD
 */

/*---------------------------------------------------------------------------
 Private functions
 ---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------
 Function codes
 ---------------------------------------------------------------------------*/
/**
 @name        sinfo_fit_1d_poly
 @memo        Fit a polynomial to a list of dpoints.
 @param    poly_deg    Degree of the polynomial to fit.
 @param    list        List of dpoints.
 @param    np            Number of points in the list.
 @param    mse            Output mean squared error.
 @return    Array of (np+1) polynomial coefficients.
 @doc

 The fitted polynomial is such that:
 \[
 P(x) = c_0 + c_1 x + c_2 x^2 + ... + c_n x^n
 \]
 So requesting a polynomial of degree n will return n+1 coefficients.
 Beware that with such polynomials, two input points shall never be
 on the same vertical!

 If you are not interested in getting the mean squared error back,
 feed in NULL instead of a pointer to a double for mse.
 */

double *
sinfo_fit_1d_poly(int poly_deg, dpoint * list, int np, double * mse)
{
    int i, k;
    Matrix mA, mB, mX;
    double * c;
    double err;
    double xp, y;

    if (np < poly_deg + 1) {
        sinfo_msg_error("not enough points");
        sinfo_msg_error("cannot fit %dth degree polynomial with %d points",
                        poly_deg, np);
        return NULL ;
    }

    mA = sinfo_create_mx(poly_deg + 1, np);
    mB = sinfo_create_mx(1, np);

    for (i = 0; i < np; i++) {
        mA->m[i] = 1.0;
        for (k = 1; k <= poly_deg; k++) {
            mA->m[i + k * np] = sinfo_ipow(list[i].x, k);
        }
        mB->m[i] = list[i].y;
    }

    /*
     * Solve XA=B by a least-square solution (aka pseudo-inverse).
     */
    mX = sinfo_least_sq_mx(mA, mB);
    /*
     * Delete input matrices
     */
    sinfo_close_mx(mA);
    sinfo_close_mx(mB);
    /*
     * Examine result
     */
    if (mX == NULL ) {
        sinfo_msg_error("cannot fit: non-invertible sinfo_matrix");
        return NULL ;
    }

    c = cpl_malloc((poly_deg + 1) * sizeof(double));
    for (i = 0; i < (poly_deg + 1); i++) {
        c[i] = mX->m[i];
    }
    sinfo_close_mx(mX);

    /*
     * If requested, compute mean squared error
     */
    if (mse != NULL ) {
        err = 0.00;
        for (i = 0; i < np; i++) {
            y = c[0];
            /*
             * Compute the value obtained through the fit
             */
            for (k = 1; k <= poly_deg; k++) {
                xp = sinfo_ipow(list[i].x, k);
                y += c[k] * xp;
            }
            /*
             * Subtract from the true value, square, accumulate
             */
            xp = sinfo_ipow(list[i].y - y, 2);
            err += xp;
        }
        /* Average the error term */
        err /= (double) np;
        *mse = err;
    }
    return c;
}

/**@}*/
