/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 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
 */

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

#include <math.h>

#include <cxtypes.h>
#include <cxmemory.h>
#include <cxmessages.h>
#include <cxstrutils.h>

#include "gimacros.h"
#include "gimodels.h"


static GiModelData _gimodels[];

const GiModelData *const giraffe_models = _gimodels;


/*
 * Optical model argument indices
 */

enum {
    LMI_WLEN = 0,
    LMI_XFIB = 1,
    LMI_YFIB = 2
};


/*
 * Optical model parameter indices
 */

enum {
    LMP_NX = 0,
    LMP_NY = 0,
    LMP_PXSIZ = 1,
    LMP_FCOLL = 2,
    LMP_CFACT = 3,
    LMP_THETA = 4,
    LMP_ORDER = 5,
    LMP_SPACE = 6,
    LMP_SOFFX = 7,
    LMP_SOFFY = 8,
    LMP_SPHI  = 9
};


/*
 * Line model parameter indices
 */

enum {
    LMP_AMPL = 0,
    LMP_CENT = 1,
    LMP_BACK = 2,
    LMP_WID1 = 3,
    LMP_WID2 = 4
};



static const cxint DW_DEGREE = 3;
static const cxdouble DW_LOG001 = 2.302585093;  /* -log(0.1) */


/*
 * Utility function to calculate a weighted exponential
 *
 *                         DW_DEGREE
 *        exp( - | x - x0 |          )
 *  w = -------------------------------
 *           / DW_DEGREE \
 *           | --------- |
 *           \ DW_LOG001 /
 *        dx
 */

inline static cxdouble
_giraffe_dydaweight(cxdouble x, cxdouble x0, cxdouble dx)
{

    register cxdouble w;


    w = exp(-pow(fabs(x - x0), DW_DEGREE) / pow(dx, DW_DEGREE / DW_LOG001));

    if (isnan(w)) {
        w = 1;
    }

    return w;

}


inline static void
_giraffe_model_dtor(GiModel *self)
{

    if (self->name) {
        cx_free(self->name);
        self->name = NULL;
    }


    self->arguments.count = 0;

    if (self->arguments.names) {
        cpl_propertylist_delete(self->arguments.names);
        self->arguments.names = NULL;
    }

    if (self->arguments.values) {
        cpl_matrix_delete(self->arguments.values);
        self->arguments.values = NULL;
    }


    self->parameters.count = 0;

    if (self->parameters.names) {
        cpl_propertylist_delete(self->parameters.names);
        self->parameters.names = NULL;
    }

    if (self->parameters.values) {
        cpl_matrix_delete(self->parameters.values);
        self->parameters.values = NULL;
    }

    if (self->parameters.limits) {
        cpl_matrix_delete(self->parameters.limits);
        self->parameters.limits = NULL;
    }

    if (self->parameters.flags) {
        cx_free(self->parameters.flags);
        self->parameters.flags = NULL;
    }


    self->fit.iterations = 0;

    if (self->fit.covariance) {
        cpl_matrix_delete(self->fit.covariance);
        self->fit.covariance = NULL;
    }

    return;

}


inline static void
_giraffe_xoptmod_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "xf", LMI_XFIB);
    cpl_propertylist_append_int(self->arguments.names, "yf", LMI_YFIB);
    cpl_propertylist_append_int(self->arguments.names, "lambda", LMI_WLEN);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Orientation",
                                LMP_NX);
    cpl_propertylist_append_int(self->parameters.names, "Order",
                                LMP_ORDER);
    cpl_propertylist_append_int(self->parameters.names, "PixelSize",
                                LMP_PXSIZ);
    cpl_propertylist_append_int(self->parameters.names, "FocalLength",
                                LMP_FCOLL);
    cpl_propertylist_append_int(self->parameters.names, "Magnification",
                                LMP_CFACT);
    cpl_propertylist_append_int(self->parameters.names, "Angle",
                                LMP_THETA);
    cpl_propertylist_append_int(self->parameters.names, "Spacing",
                                LMP_SPACE);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_xoptmod_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                      cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_xoptmod_eval";


    cxdouble lambda;
    cxdouble xfibre;
    cxdouble yfibre;

    cxdouble pixsize, nx;
    cxdouble fcoll, cfact;
    cxdouble gtheta, gorder, gspace;

    register cxdouble xccd, d, X;
    register cxdouble yfibre2, tmp, tmp2, d2, X2, gspace2;
    register cxdouble sqtmp, costheta, sintheta;



    if (na != 7) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_NX] = 0.;
        dyda[LMP_PXSIZ] = 0.;
        dyda[LMP_FCOLL] = 0.;
        dyda[LMP_CFACT] = 0.;
        dyda[LMP_THETA] = 0.;
        dyda[LMP_ORDER] = 0.;
        dyda[LMP_SPACE] = 0.;
    }

    lambda = x[LMI_WLEN];  /* wavelength [nm] */
    xfibre = x[LMI_XFIB];  /* X fibre [mm] */
    yfibre = x[LMI_YFIB];  /* Y fibre [mm] */

    nx = a[LMP_NX];             /* CCD  size along X [pixels] */
    pixsize = a[LMP_PXSIZ];     /* CCD pixel size [mm] */
    fcoll = a[LMP_FCOLL];       /* collimator focal length [mm] */
    cfact = a[LMP_CFACT];       /* camera magnification factor */
    gtheta = a[LMP_THETA];      /* grating angle [radian] */
    gorder = a[LMP_ORDER];      /* grating diffraction order */
    gspace = a[LMP_SPACE];      /* grating groove spacing [mm] */


    lambda *= GI_NM_TO_MM;  /* wavelength [mm] */

    yfibre2  = yfibre * yfibre;
    gspace2  = gspace * gspace;
    costheta = cos(gtheta);
    sintheta = sin(gtheta);

    d2 = xfibre * xfibre + yfibre2 + (fcoll * fcoll);
    d  = sqrt(d2);

    X  = (-lambda * gorder / gspace) + (xfibre * costheta / d) +
        (fcoll * sintheta / d);
    X2 = X * X;

    sqtmp = sqrt(1.0 - yfibre2 / d2 - X2);
    tmp   = -sintheta * X + costheta * sqtmp;
    tmp2  = tmp * tmp;
    xccd  = (cfact * fcoll * (X * costheta + sintheta * sqtmp)) / tmp;


    /*
     * Take care of model direction
     */

    if (nx < 0.0) {
        *y = xccd / pixsize - 0.5 * nx;
    }
    else {
        *y = -xccd / pixsize + 0.5 * nx;
    }


    /*
     * If requested, compute the partial derivatives of y
     * with respect to each parameter.
     */

    if (dyda != NULL) {

        dyda[LMP_NX]    = 0.5;
        dyda[LMP_PXSIZ] = 0.0;

        dyda[LMP_FCOLL] = cfact * (costheta * X + sintheta * sqtmp) / tmp +
            cfact * fcoll * (costheta * (-X * fcoll / d2 + sintheta / d -
                                         gorder * lambda * fcoll /
                                         (d2 * gspace)) + 0.5 * sintheta *
                             (-2.0 * X * (-X * fcoll / d2 + sintheta / d -
                                          gorder * lambda * fcoll /
                                          (d2 * gspace)) +
                              2.0 * yfibre2 * fcoll / (d2 * d2)) / sqtmp) /
            tmp - cfact * fcoll * (costheta * X + sintheta * sqtmp) *
            (-sintheta * (-X * fcoll / d2 + sintheta / d - gorder * lambda *
                          fcoll / (d2 * gspace)) + 0.5 * costheta *
             (-2.0 * X * (-X * fcoll / d2 + sintheta / d - gorder * lambda *
                          fcoll / (d2 * gspace)) + 2.0 * yfibre2 * fcoll /
              (d2 * d2)) / sqtmp) / tmp2;

        dyda[LMP_FCOLL] /= pixsize;
        dyda[LMP_CFACT] = (xccd / cfact) / pixsize;

        dyda[LMP_THETA] = cfact * fcoll * ((-xfibre * sintheta / d + fcoll *
                                            costheta / d) * costheta -
                                           sintheta * X - sintheta * X *
                                           (-xfibre * sintheta / d + fcoll *
                                            costheta / d) / sqtmp +
                                           costheta * sqtmp) / tmp -
            cfact * fcoll * (costheta * X + sintheta * sqtmp) *
            (-(-xfibre * sintheta / d + fcoll * costheta / d) * sintheta -
             costheta * X - costheta * X * (-xfibre * sintheta / d + fcoll *
                                            costheta / d) /
             sqtmp - sintheta * sqtmp) / tmp2;

        dyda[LMP_THETA] /= pixsize;
        dyda[LMP_ORDER] = 0.0;

        dyda[LMP_SPACE] = cfact * fcoll * (lambda * gorder * costheta /
                                           gspace2 - sintheta * X * lambda *
                                           gorder / (sqtmp * gspace2)) /
            tmp - cfact * fcoll * (X * costheta + sintheta * sqtmp) *
            (-lambda * gorder * sintheta / gspace2 - costheta * X * lambda *
             gorder / (sqtmp * gspace2)) / tmp2;

        dyda[LMP_SPACE] /= pixsize;

        if (nx > 0.) {
            dyda[LMP_NX]    = -dyda[LMP_NX];
            dyda[LMP_PXSIZ] = -dyda[LMP_PXSIZ];
            dyda[LMP_FCOLL] = -dyda[LMP_FCOLL];
            dyda[LMP_CFACT] = -dyda[LMP_CFACT];
            dyda[LMP_THETA] = -dyda[LMP_THETA];
            dyda[LMP_ORDER] = -dyda[LMP_ORDER];
            dyda[LMP_SPACE] = -dyda[LMP_SPACE];
        }

        if (r != NULL) {

            register cxint k;

            k = LMP_FCOLL << 1;
            if (r[k+1] > 0) {
                dyda[LMP_FCOLL] *= _giraffe_dydaweight(a[LMP_FCOLL], r[k],
                                                       r[k + 1]);
            }

            k = LMP_CFACT << 1;
            if (r[k+1] > 0) {
                dyda[LMP_CFACT] *= _giraffe_dydaweight(a[LMP_CFACT], r[k],
                                                       r[k + 1]);
            }

            k = LMP_THETA << 1;
            if (r[k+1] > 0) {
                dyda[LMP_THETA] *= _giraffe_dydaweight(a[LMP_THETA], r[k],
                                                       r[k + 1]);
            }

            k = LMP_SPACE << 1;
            if (r[k+1] > 0) {
                dyda[LMP_SPACE] *= _giraffe_dydaweight(a[LMP_SPACE], r[k],
                                                       r[k + 1]);
            }

        }

    }

    return;

}


inline static void
_giraffe_yoptmod_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "xf", LMI_XFIB);
    cpl_propertylist_append_int(self->arguments.names, "yf", LMI_YFIB);
    cpl_propertylist_append_int(self->arguments.names, "lambda", LMI_WLEN);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Orientation",
                                LMP_NY);
    cpl_propertylist_append_int(self->parameters.names, "Order",
                                LMP_ORDER);
    cpl_propertylist_append_int(self->parameters.names, "PixelSize",
                                LMP_PXSIZ);
    cpl_propertylist_append_int(self->parameters.names, "FocalLength",
                                LMP_FCOLL);
    cpl_propertylist_append_int(self->parameters.names, "Magnification",
                                LMP_CFACT);
    cpl_propertylist_append_int(self->parameters.names, "Angle",
                                LMP_THETA);
    cpl_propertylist_append_int(self->parameters.names, "Spacing",
                                LMP_SPACE);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_yoptmod_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                      cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_yoptmod_eval";

    cxdouble lambda, xfibre, yfibre;
    cxdouble pixsize, ny;
    cxdouble fcoll,cfact;
    cxdouble gtheta,gorder,gspace;

    register cxdouble t2, t3, t4, t5, t6, t7, t8, t9;
    register cxdouble t10, t12, t13, t15, t18;
    register cxdouble t22, t24, t26, t27, t28, t29;
    register cxdouble t30, t33;
    register cxdouble t41, t45, t47;
    register cxdouble t53, t56, t57;
    register cxdouble t76;
    register cxdouble t93, t94;


    /* Not used */
    (void) r;

    if (na != 7) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.;

    if (dyda != NULL) {
        dyda[LMP_NY] = 0.;
        dyda[LMP_PXSIZ] = 0.;
        dyda[LMP_FCOLL] = 0.;
        dyda[LMP_CFACT] = 0.;
        dyda[LMP_THETA] = 0.;
        dyda[LMP_ORDER] = 0.;
        dyda[LMP_SPACE] = 0.;
    }

    lambda  = x[LMI_WLEN];
    xfibre  = x[LMI_XFIB];
    yfibre  = x[LMI_YFIB];

    ny      = a[LMP_NY];
    pixsize = a[LMP_PXSIZ];
    fcoll   = a[LMP_FCOLL];
    cfact   = a[LMP_CFACT];
    gtheta  = a[LMP_THETA];
    gorder  = a[LMP_ORDER];
    gspace  = a[LMP_SPACE];

    lambda *= GI_NM_TO_MM;

    t2 = cfact * fcoll * yfibre;
    t3 = xfibre * xfibre;
    t4 = yfibre * yfibre;
    t5 = fcoll * fcoll;
    t6 = t3 + t4 + t5;
    t7 = sqrt(t6);
    t8 = 1.0 / t7;
    t9 = lambda * gorder;
    t10 = 1.0 / gspace;
    t12 = cos(gtheta);
    t13 = xfibre * t12;
    t15 = sin(gtheta);
    t18 = -t9 * t10 + t13 * t8 + fcoll * t15 * t8;
    t22 = t18 * t18;
    t24 = sqrt(1.0 - t4 / t6 - t22);
    t26 = -t18 * t15 + t12 * t24;
    t27 = 1.0 / t26;
    t28 = t8 * t27;
    t29 = 1.0 / pixsize;
    t30 = t28 * t29;
    t33 = pixsize * pixsize;
    t41 = 1.0 / t7 / t6;
    t45 = t26 * t26;
    t47 = t8 / t45;
    t53 = -t13 * t41 * fcoll + t15 * t8 - t5 * t15 * t41;
    t56 = t12 / t24;
    t57 = t6 * t6;
    t76 = -xfibre * t15 * t8 + fcoll * t12 * t8;
    t93 = gspace * gspace;
    t94 = 1.0 / t93;

    *y = -t2 * t30 + 0.5 * ny;


    /*
     * If requested, compute the partial derivatives of y
     * with respect to each parameter.
     */

    if (dyda != NULL) {

        dyda[LMP_NY] = 0.5;
        dyda[LMP_PXSIZ] = t2 * t28 / t33;
        dyda[LMP_FCOLL] = -cfact * yfibre * t30 + cfact * t5 *
            yfibre * t41 * t27 * t29 + t2 * t47 * t29 *
            (-t53 * t15 + t56 * (2.0 * t4 / t57 * fcoll -
                                 2.0 * t18 * t53) / 2.0);
        dyda[LMP_CFACT] = -fcoll * yfibre * t30;
        dyda[LMP_THETA] = t2 * t47 * t29 * (-t76 * t15 - t18 * t12 -
                                            t15 * t24 - t56 * t18 * t76);
        dyda[LMP_ORDER] = t2 * t47 *t29 *(lambda * t10 * t15 + t56 *
                                          t18 * lambda * t10);
        dyda[LMP_SPACE] = t2 * t47 * t29 * (-t9 * t94 * t15 -
                                            t56 * t18 * t9 * t94);

    }

    return;

}


inline static void
_giraffe_xoptmod2_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "xf", LMI_XFIB);
    cpl_propertylist_append_int(self->arguments.names, "yf", LMI_YFIB);
    cpl_propertylist_append_int(self->arguments.names, "lambda", LMI_WLEN);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Orientation",
                                LMP_NX);
    cpl_propertylist_append_int(self->parameters.names, "Order",
                                LMP_ORDER);
    cpl_propertylist_append_int(self->parameters.names, "PixelSize",
                                LMP_PXSIZ);
    cpl_propertylist_append_int(self->parameters.names, "FocalLength",
                                LMP_FCOLL);
    cpl_propertylist_append_int(self->parameters.names, "Magnification",
                                LMP_CFACT);
    cpl_propertylist_append_int(self->parameters.names, "Angle",
                                LMP_THETA);
    cpl_propertylist_append_int(self->parameters.names, "Spacing",
                                LMP_SPACE);
    cpl_propertylist_append_int(self->parameters.names, "Sdx",
                                LMP_SOFFX);
    cpl_propertylist_append_int(self->parameters.names, "Sdy",
                                LMP_SOFFY);
    cpl_propertylist_append_int(self->parameters.names, "Sphi",
                                LMP_SPHI);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_xoptmod2_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                       cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_xoptmod2_eval";


    cxdouble lambda;
    cxdouble xfibre;
    cxdouble yfibre;

    cxdouble pixsize, nx;
    cxdouble fcoll, cfact;
    cxdouble gtheta, gorder, gspace;
    cxdouble slitdx, slitdy, slitphi;

    register cxdouble t1, t2, t3, t4, t9;
    register cxdouble t10, t11, t12, t14, t16, t17, t18, t19;
    register cxdouble t20, t21, t23, t24, t26, t27, t28;
    register cxdouble t30, t32, t33, t34, t35, t36, t37, t38, t39;
    register cxdouble t40, t44, t49;
    register cxdouble t52, t58;
    register cxdouble t60, t61, t62, t64, t68;
    register cxdouble t75, t76, t78;
    register cxdouble t80;
    register cxdouble t91, t93;
    register cxdouble t104, t107;
    register cxdouble t113, t119;
    register cxdouble t120, t121, t124;
    register cxdouble t136, t137, t138;
    register cxdouble t143, t148;
    register cxdouble t161, t162, t166, t168;
    register cxdouble t173;
    register cxdouble t191, t195, t196;
    register cxdouble t201, t210;


    if (na != 10) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_NX] = 0.;
        dyda[LMP_PXSIZ] = 0.;
        dyda[LMP_FCOLL] = 0.;
        dyda[LMP_CFACT] = 0.;
        dyda[LMP_THETA] = 0.;
        dyda[LMP_ORDER] = 0.;
        dyda[LMP_SPACE] = 0.;
        dyda[LMP_SOFFX] = 0.;
        dyda[LMP_SOFFY] = 0.;
        dyda[LMP_SPHI] = 0.;
    }

    lambda  = x[LMI_WLEN];    /* wavelength [nm] */
    xfibre  = x[LMI_XFIB];    /* Y fibre [mm] */
    yfibre  = x[LMI_YFIB];    /* Y fibre [mm] */

    nx      = a[LMP_NX];      /* CCD  size in X [pixels] */
    pixsize = a[LMP_PXSIZ];   /* CCD pixel size [mm] */
    fcoll   = a[LMP_FCOLL];   /* collimator focal length [mm] */
    cfact   = a[LMP_CFACT];   /* camera magnification factor */
    gtheta  = a[LMP_THETA];   /* grating angle [radian] */
    gorder  = a[LMP_ORDER];   /* grating diffraction order */
    gspace  = a[LMP_SPACE];   /* grating groove spacing [mm] */
    slitdx  = a[LMP_SOFFX];   /* slit position x offset [mm] */
    slitdy  = a[LMP_SOFFY];   /* slit position y offset [mm] */
    slitphi = a[LMP_SPHI];    /* slit position angle [radian] */

    lambda *= GI_NM_TO_MM;  /* wavelength [mm] */

    t1 = cfact * fcoll;
    t2 = cos(gtheta);
    t3 = lambda * gorder;
    t4 = 1.0 / gspace;
    t9 = xfibre * (1.0 + slitphi * yfibre) + slitdx;
    t10 = t9 * t2;
    t11 = t9 * t9;
    t12 = slitphi * slitphi;
    t14 = sqrt(1.0 - t12);
    t16 = yfibre * t14 + slitdy;
    t17 = t16 * t16;
    t18 = fcoll * fcoll;
    t19 = t11 + t17 + t18;
    t20 = sqrt(t19);
    t21 = 1.0 / t20;
    t23 = sin(gtheta);
    t24 = fcoll * t23;
    t26 = -t3 * t4 + t10 * t21 + t24 * t21;
    t27 = t2 * t26;
    t28 = 1.0 / t19;
    t30 = t26 * t26;
    t32 = sqrt(1.0 - t17 * t28 - t30);
    t33 = t23 * t32;
    t34 = t27 + t33;
    t35 = t23 * t26;
    t36 = t2 * t32;
    t37 = -t35 + t36;
    t38 = 1.0 / t37;
    t39 = t34 * t38;
    t40 = 1.0 / pixsize;
    t44 = pixsize * pixsize;
    t49 = t38 * t40;
    t52 = 1.0 / t20 / t19;
    t58 = -t10 * t52 * fcoll + t23 * t21 - t18 * t23 * t52;
    t60 = 1.0 / t32;
    t61 = t23 * t60;
    t62 = t19 * t19;
    t64 = t17 / t62;
    t68 = 2.0 * t64 * fcoll - 2.0 * t26 * t58;
    t75 = t1 * t34;
    t76 = t37 * t37;
    t78 = 1.0 / t76 * t40;
    t80 = t2 * t60;
    t91 = -t9 * t23 * t21 + fcoll * t2 * t21;
    t93 = t26 * t91;
    t104 = t2 * lambda;
    t107 = t26 * lambda * t4;
    t113 = t23 * lambda;
    t119 = gspace * gspace;
    t120 = 1.0 / t119;
    t121 = gorder * t120;
    t124 = t3 * t120;
    t136 = t2 * t21;
    t137 = 2.0 * t9;
    t138 = t52 * t137;
    t143 = t136 - t10 * t138 / 2.0 - t24 * t138 / 2.0;
    t148 = t64 * t137 - 2.0 * t26 * t143;
    t161 = 2.0 * t16;
    t162 = t52 * t161;
    t166 = -t10 * t162 / 2.0 - t24 * t162 / 2.0;
    t168 = t16 * t28;
    t173 = -2.0 * t168 + t64 * t161 - 2.0 * t26 * t166;
    t191 = 1.0 / t14;
    t195 = 2.0 * t9 * xfibre * yfibre - 2.0 * t16 * yfibre * t191 * slitphi;
    t196 = t52 * t195;
    t201 = xfibre * yfibre * t136 - t10 * t196 / 2.0 - t24 * t196 / 2.0;
    t210 = 2.0 * t168 * yfibre * t191 * slitphi + t64 * t195 -
        2.0 * t26 * t201;


    /*
     * Take care of model direction
     */

    if (nx < 0.0) {
        *y = t1 * t39 * t40 - 0.5 * nx;
    }
    else {
        *y = -t1 * t39 * t40 + 0.5 * nx;
    }


    /*
     * If requested, compute the partial derivatives of y
     * with respect to each parameter.
     */

    if (dyda != NULL) {

        dyda[LMP_NX] = 0.5;
        dyda[LMP_PXSIZ] = -t1 * t39 / t44;
        dyda[LMP_FCOLL] = cfact * t34 * t49 + t1 *
            (t2 * t58 + t61 * t68 / 2.0) * t38 * t40 -
            t75 * t78 * (-t23 * t58 + t80 * t68 / 2.0);
        dyda[LMP_CFACT] = fcoll * t34 * t49;
        dyda[LMP_THETA] = t1 * (-t35 + t2 * t91 + t36 - t61 * t93) * t38 *
            t40 - t75 * t78 * (-t27 - t23 * t91 - t33 - t80 * t93);
        dyda[LMP_ORDER] = t1 * (-t104 * t4 + t61 * t107) * t38 * t40 - t75 *
            t78 * (t113 * t4 + t80 * t107);
        dyda[LMP_SPACE] = t1 * (t104 * t121 - t61 * t26 * t124) * t38 * t40 -
            t75 * t78 * (-t113 * t121 - t80 * t26 * t124);
        dyda[LMP_SOFFX] = t1 * (t2 * t143 + t61 * t148 / 2.0) * t38 * t40 -
            t75 * t78 * (-t23 * t143 + t80 * t148 / 2.0);
        dyda[LMP_SOFFY] = t1 * (t2 * t166 + t61 * t173 / 2.0) * t38 * t40 -
            t75 * t78 * (-t23 * t166 + t80 * t173 / 2.0);
        dyda[LMP_SPHI]  = t1 * (t2 * t201 + t61 * t210 / 2.0) * t38 * t40 -
            t75 * t78 * (-t23 * t201 + t80 * t210 / 2.0);

        if (nx > 0.0) {
            dyda[LMP_NX]    = -dyda[LMP_NX];
            dyda[LMP_PXSIZ] = -dyda[LMP_PXSIZ];
            dyda[LMP_FCOLL] = -dyda[LMP_FCOLL];
            dyda[LMP_CFACT] = -dyda[LMP_CFACT];
            dyda[LMP_THETA] = -dyda[LMP_THETA];
            dyda[LMP_ORDER] = -dyda[LMP_ORDER];
            dyda[LMP_SPACE] = -dyda[LMP_SPACE];
            dyda[LMP_SOFFX] = -dyda[LMP_SOFFX];
            dyda[LMP_SOFFY] = -dyda[LMP_SOFFY];
            dyda[LMP_SPHI]  = -dyda[LMP_SPHI];
        }

        if (r != NULL) {

            register cxint k;


            k = LMP_PXSIZ << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_PXSIZ] *= _giraffe_dydaweight(a[LMP_PXSIZ], r[k],
                                                       r[k + 1]);
            }

            k = LMP_FCOLL << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_FCOLL] *= _giraffe_dydaweight(a[LMP_FCOLL], r[k],
                                                       r[k + 1]);
            }

            k = LMP_CFACT << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_CFACT] *= _giraffe_dydaweight(a[LMP_CFACT], r[k],
                                                       r[k + 1]);
            }

            k = LMP_THETA << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_THETA] *=  _giraffe_dydaweight(a[LMP_THETA], r[k],
                                                        r[k + 1]);
            }

            k = LMP_ORDER << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_ORDER] *=  _giraffe_dydaweight(a[LMP_ORDER], r[k],
                                                        r[k + 1]);
            }

            k = LMP_SPACE << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_SPACE] *=  _giraffe_dydaweight(a[LMP_SPACE], r[k],
                                                        r[k + 1]);
            }

            k = LMP_SOFFX << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_SOFFX] *=  _giraffe_dydaweight(a[LMP_SOFFX], r[k],
                                                        r[k + 1]);
            }

            k = LMP_SOFFY << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_SOFFY] *=  _giraffe_dydaweight(a[LMP_SOFFY], r[k],
                                                        r[k + 1]);
            }

            k = LMP_SPHI << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_SPHI] *=  _giraffe_dydaweight(a[LMP_SPHI], r[k],
                                                       r[k + 1]);
            }

        }

    }

    return;

}


inline static void
_giraffe_yoptmod2_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "xf", LMI_XFIB);
    cpl_propertylist_append_int(self->arguments.names, "yf", LMI_YFIB);
    cpl_propertylist_append_int(self->arguments.names, "lambda", LMI_WLEN);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Orientation",
                                LMP_NY);
    cpl_propertylist_append_int(self->parameters.names, "Order",
                                LMP_ORDER);
    cpl_propertylist_append_int(self->parameters.names, "PixelSize",
                                LMP_PXSIZ);
    cpl_propertylist_append_int(self->parameters.names, "FocalLength",
                                LMP_FCOLL);
    cpl_propertylist_append_int(self->parameters.names, "Magnification",
                                LMP_CFACT);
    cpl_propertylist_append_int(self->parameters.names, "Angle",
                                LMP_THETA);
    cpl_propertylist_append_int(self->parameters.names, "Spacing",
                                LMP_SPACE);
    cpl_propertylist_append_int(self->parameters.names, "Sdx",
                                LMP_SOFFX);
    cpl_propertylist_append_int(self->parameters.names, "Sdy",
                                LMP_SOFFY);
    cpl_propertylist_append_int(self->parameters.names, "Sphi",
                                LMP_SPHI);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_yoptmod2_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                       cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_yoptmod2_eval";


    cxdouble lambda, xfibre, yfibre;
    cxdouble pixsize, ny;
    cxdouble fcoll, cfact;
    cxdouble gtheta, gorder, gspace;
    cxdouble slitdx, slitdy, slitphi;

    register cxdouble t1, t2, t4, t6, t7;
    register cxdouble t11, t12, t13, t14, t15, t16, t17, t18, t19;
    register cxdouble t21, t22, t24, t25, t27, t29;
    register cxdouble t31, t33, t35, t36, t37, t38, t39;
    register cxdouble t42, t50, t51, t54, t56;
    register cxdouble t62, t65, t66, t68;
    register cxdouble t85;
    register cxdouble t102, t103;
    register cxdouble t112, t117, t118;
    register cxdouble t123;
    register cxdouble t136;
    register cxdouble t141, t145, t147;
    register cxdouble t159;
    register cxdouble t160;
    register cxdouble t172, t179;
    register cxdouble t184;


    /* Not used */
    (void) r;

    if (na != 10) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_NY] = 0.;
        dyda[LMP_PXSIZ] = 0.;
        dyda[LMP_FCOLL] = 0.;
        dyda[LMP_CFACT] = 0.;
        dyda[LMP_THETA] = 0.;
        dyda[LMP_ORDER] = 0.;
        dyda[LMP_SPACE] = 0.;
        dyda[LMP_SOFFX] = 0.;
        dyda[LMP_SOFFY] = 0.;
        dyda[LMP_SPHI] = 0.;
    }

    lambda  = x[LMI_WLEN];
    xfibre  = x[LMI_XFIB];
    yfibre  = x[LMI_YFIB];

    ny      = a[LMP_NY];
    pixsize = a[LMP_PXSIZ];
    fcoll   = a[LMP_FCOLL];
    cfact   = a[LMP_CFACT];
    gtheta  = a[LMP_THETA];
    gorder  = a[LMP_ORDER];
    gspace  = a[LMP_SPACE];
    slitdx  = a[LMP_SOFFX];
    slitdy  = a[LMP_SOFFY];
    slitphi = a[LMP_SPHI];

    lambda *= GI_NM_TO_MM;

    t1 = cfact * fcoll;
    t2 = slitphi * slitphi;
    t4 = sqrt(1.0 - t2);
    t6 = yfibre * t4 + slitdy;
    t7 = t1 * t6;
    t11 = xfibre * (1.0 + slitphi * yfibre) + slitdx;
    t12 = t11 * t11;
    t13 = t6 * t6;
    t14 = fcoll * fcoll;
    t15 = t12 + t13 + t14;
    t16 = sqrt(t15);
    t17 = 1 / t16;
    t18 = lambda * gorder;
    t19 = 1 / gspace;
    t21 = cos(gtheta);
    t22 = t11 * t21;
    t24 = sin(gtheta);
    t25 = fcoll * t24;
    t27 = -t18 * t19 + t22 * t17 + t25 * t17;
    t29 = 1 / t15;
    t31 = t27 * t27;
    t33 = sqrt(1.0 - t13 * t29 - t31);
    t35 = -t27 * t24 + t21 * t33;
    t36 = 1 / t35;
    t37 = t17 * t36;
    t38 = 1 / pixsize;
    t39 = t37 * t38;
    t42 = pixsize * pixsize;
    t50 = 1 / t16 / t15;
    t51 = t50 * t36;
    t54 = t35 * t35;
    t56 = t17 / t54;
    t62 = -t22 * t50 * fcoll + t24 * t17 - t14 * t24 * t50;
    t65 = t21 / t33;
    t66 = t15 * t15;
    t68 = t13 / t66;
    t85 = -t11 * t24 * t17 + fcoll * t21 * t17;
    t102 = gspace * gspace;
    t103 = 1 / t102;
    t112 = 2.0 * t11;
    t117 = t21 * t17;
    t118 = t50 * t112;
    t123 = t117 - t22 * t118 / 2.0 - t25 * t118 / 2.0;
    t136 = 2.0 * t6;
    t141 = t50 * t136;
    t145 = -t22 * t141 / 2.0 - t25 * t141 / 2.0;
    t147 = t6 * t29;
    t159 = 1 / t4;
    t160 = yfibre * t159;
    t172 = 2.0 * t11 * xfibre * yfibre - 2.0 * t6 * yfibre * t159 * slitphi;
    t179 = t50 * t172;
    t184 = xfibre * yfibre * t117 - t22 * t179 / 2.0 - t25 * t179 / 2.0;

    *y = -t7 * t39 + 0.5 * ny;

    if (dyda != NULL) {

        dyda[LMP_NY] = 0.5;
        dyda[LMP_PXSIZ] = t7 * t37 / t42;
        dyda[LMP_FCOLL] = -cfact * t6 * t39 + cfact * t14 * t6 * t51 * t38 +
            t7 * t56 * t38 * (-t62 * t24 + t65 * (2.0 * t68 * fcoll -
                                                  2.0 * t27 * t62) / 2.0);
        dyda[LMP_CFACT] = -fcoll * t6 * t39;
        dyda[LMP_THETA] = t7 * t56 * t38 * (-t85 * t24 - t27 * t21 - t24 *
                                            t33 - t65 * t27 * t85);
        dyda[LMP_ORDER] = t7 * t56 * t38 * (lambda * t19 * t24 + t65 * t27 *
                                            lambda * t19);
        dyda[LMP_SPACE] = t7 * t56 * t38 * (-t18 * t103 * t24 - t65 * t27 *
                                            t18 * t103);
        dyda[LMP_SOFFX] = t7 * t51 * t38 * t112 / 2.0 + t7 * t56 * t38 *
            (-t123 * t24 + t65 * (t68 * t112 - 2.0 * t27 * t123) / 2.0);
        dyda[LMP_SOFFY] = -t1 * t39 + t7 * t51 * t38 * t136 / 2.0 + t7 *
            t56 * t38 * (-t145 * t24 + t65 * (-2.0 * t147 + t68 * t136 -
                                              2.0 * t27 * t145) / 2.0);
        dyda[LMP_SPHI]  = t1 * t160 * slitphi * t17 * t36 * t38 + t7 * t51 *
            t38 * t172 / 2.0 + t7 * t56 * t38 *
            (-t184 * t24 + t65 * (2.0 * t147 * t160 * slitphi + t68 * t172 -
                                  2.0 * t27 * t184) / 2.0);

    }

    return;

}


inline static void
_giraffe_gaussian_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "x", 0);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Amplitude",
                                LMP_AMPL);
    cpl_propertylist_append_int(self->parameters.names, "Center",
                                LMP_CENT);
    cpl_propertylist_append_int(self->parameters.names, "Background",
                                LMP_BACK);
    cpl_propertylist_append_int(self->parameters.names, "Width1",
                                LMP_WID1);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_gaussian_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                       cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_gaussian_eval";


    cxdouble fac;
    cxdouble ex;
    cxdouble amplitude;
    cxdouble center;
    cxdouble backg;
    cxdouble width;
    cxdouble xred;


    /* Not used */
    (void) r;

    if (na != 4) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_AMPL] = 0.;
        dyda[LMP_CENT] = 0.;
        dyda[LMP_BACK] = 0.;
        dyda[LMP_WID1] = 0.;
    }


    amplitude = a[LMP_AMPL];
    center    = a[LMP_CENT];
    backg     = a[LMP_BACK];
    width     = a[LMP_WID1];

    xred = (x[0] - center) / width;

    ex = exp(-xred * xred / 2.);
    fac = amplitude * xred * ex;

    *y = amplitude * ex + backg;


    /*
     * If requested, compute the partial derivatives of y
     * with respect to each parameter.
     */

    if (dyda != NULL) {

        dyda[LMP_AMPL] = ex;                      /* d(y)/d(amplitude) */
        dyda[LMP_CENT] = fac / width;             /* d(y)/d(center) */
        dyda[LMP_BACK] = 1.;                      /* d(y)/d(backg) */
        dyda[LMP_WID1] = (fac * xred) / width;    /* d(y)/d(width) */

    }

    return;

}


inline static void
_giraffe_psfcos_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "x", 0);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Amplitude",
                                LMP_AMPL);
    cpl_propertylist_append_int(self->parameters.names, "Center",
                                LMP_CENT);
    cpl_propertylist_append_int(self->parameters.names, "Background",
                                LMP_BACK);
    cpl_propertylist_append_int(self->parameters.names, "Width1",
                                LMP_WID1);
    cpl_propertylist_append_int(self->parameters.names, "Width2",
                                LMP_WID2);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_psfcos_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                     cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_psfcos_eval";


    cxdouble amplitude;
    cxdouble center;
    cxdouble background;
    cxdouble width1;
    cxdouble width2;

    cxdouble t1, t2, t3, t4, t5, t6, t7, t8, t9;
    cxdouble t10, t13, t14, t15, t16;
    cxdouble t26;


    /* Not used */
    (void) r;

    if (na != 5) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_AMPL] = 0.;
        dyda[LMP_CENT] = 0.;
        dyda[LMP_BACK] = 0.;
        dyda[LMP_WID1] = 0.;
        dyda[LMP_WID2] = 0.;
    }

    amplitude  = a[LMP_AMPL];
    center     = a[LMP_CENT];
    background = a[LMP_BACK];
    width1     = a[LMP_WID1];
    width2     = a[LMP_WID2];

    t1 = x[0] - center;
    t2 = fabs(t1);
    t3 = 1.0 / width2;
    t4 = t2 * t3;
    t5 = pow(t4, width1);
    t6 = CX_PI * t5;
    t7 = cos(t6);
    t8 = 1.0 + t7;
    t9 = t8 * t8;
    t10 = t9 * t8;
    t13 = amplitude * t9;
    t14 = sin(t6);
    t15 = t13 * t14;
    t16 = log(t4);
    t26 = t1 > 0.0 ? 1.0 : -1.0;

    if (t2 > width2) {
        *y =  background;

        if (dyda != NULL) {
            dyda[LMP_WID2] = 1.0;
        }
    }
    else {
        *y = amplitude * t10 / 8.0 + background;

        if (dyda != NULL) {

            dyda[LMP_AMPL] = t10 / 8.0;
            dyda[LMP_CENT] = 3.0 / 8.0 * t13 * t14 * CX_PI * t5 *
                width1 * t26 / t2;
            dyda[LMP_BACK] = 1.0;
            dyda[LMP_WID1] = -3.0 / 8.0 * t15 * t6 * t16;
            dyda[LMP_WID2] = 3.0 / 8.0 * t15 * t6 * width1 * t3;

        }
    }

    return;

}


inline static void
_giraffe_psfexp_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "x", 0);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Amplitude",
                                LMP_AMPL);
    cpl_propertylist_append_int(self->parameters.names, "Center",
                                LMP_CENT);
    cpl_propertylist_append_int(self->parameters.names, "Background",
                                LMP_BACK);
    cpl_propertylist_append_int(self->parameters.names, "Width1",
                                LMP_WID1);
    cpl_propertylist_append_int(self->parameters.names, "Width2",
                                LMP_WID2);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_psfexp_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                     cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_psfexp_eval";

    cxdouble amplitude;
    cxdouble center;
    cxdouble background;
    cxdouble width1;
    cxdouble width2;

    cxdouble t1, t2, t3, t4, t6, t8;
    cxdouble t10, t15, t18;


    r = NULL;

    if (na != 5) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_AMPL] = 0.;
        dyda[LMP_CENT] = 0.;
        dyda[LMP_BACK] = 0.;
        dyda[LMP_WID1] = 0.;
        dyda[LMP_WID2] = 0.;
    }

    amplitude  = a[LMP_AMPL];
    center     = a[LMP_CENT];
    background = a[LMP_BACK];
    width1     = a[LMP_WID1];
    width2     = a[LMP_WID2];

    t1 = x[0] - center;

    if (t1 > 0.0) {
        t2 = t1;
        t10 = 1.0;
    }
    else {
        t2 = -t1;
        t10 = -1.0;
    }

    t3 = pow(t2, width2);
    t4 = 1.0 / width1;
    t6 = exp(-t3 * t4);
    t8 = amplitude * t3;
    t15 = width1 * width1;
    t18 = log(t2);

    *y = amplitude * t6 + background;

    if (dyda != NULL) {
        dyda[LMP_AMPL] = t6;
        dyda[LMP_BACK] = 1.0;

        dyda[LMP_CENT] = t8 * width2 * t10 / t2 * t4 * t6;

        if (isnan(dyda[LMP_CENT])) {
            dyda[LMP_CENT] = 0.;
        }

        dyda[LMP_WID1] = t8 / t15 * t6;

        if (isnan(dyda[LMP_WID1])) {
            dyda[LMP_WID1] = 0.;
        }

        dyda[LMP_WID2] = -t8 * t18 * t4 * t6;

        if (isnan(dyda[LMP_WID2])) {
            dyda[LMP_WID2] = 0.;
        }

        if (r != NULL) {

            register cxint k;

            k = LMP_AMPL << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_AMPL] *= _giraffe_dydaweight(a[LMP_AMPL], r[k],
                                                      r[k + 1]);
            }

            k = LMP_CENT << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_CENT] *= _giraffe_dydaweight(a[LMP_CENT], r[k],
                                                      r[k + 1]);
            }

            k = LMP_WID1 << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_WID1] *= _giraffe_dydaweight(a[LMP_WID1], r[k],
                                                      r[k + 1]);
            }

            k = LMP_WID2 << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_WID2] *= _giraffe_dydaweight(a[LMP_WID2], r[k],
                                                      r[k + 1]);
            }

        }

    }

    return;

}


inline static void
_giraffe_psfexp2_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "x", 0);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Amplitude",
                                LMP_AMPL);
    cpl_propertylist_append_int(self->parameters.names, "Center",
                                LMP_CENT);
    cpl_propertylist_append_int(self->parameters.names, "Background",
                                LMP_BACK);
    cpl_propertylist_append_int(self->parameters.names, "Width1",
                                LMP_WID1);
    cpl_propertylist_append_int(self->parameters.names, "Width2",
                                LMP_WID2);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_psfexp2_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                      cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_psfexp2_eval";

    cxdouble amplitude;
    cxdouble center;
    cxdouble background;
    cxdouble width1;
    cxdouble width2;

    cxdouble t1, t2, t3, t4, t5, t6, t8;
    cxdouble t10, t16;


    r = NULL;

    if (na != 5) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[LMP_AMPL] = 0.;
        dyda[LMP_CENT] = 0.;
        dyda[LMP_BACK] = 0.;
        dyda[LMP_WID1] = 0.;
        dyda[LMP_WID2] = 0.;
    }

    amplitude  = a[LMP_AMPL];
    center     = a[LMP_CENT];
    background = a[LMP_BACK];
    width1     = a[LMP_WID1];
    width2     = a[LMP_WID2];

    t1 = x[0] - center;

    if (t1 > 0.0) {
        t2 = t1;
        t10 = 1.0;
    }
    else {
        t2 = -t1;
        t10 = -1.0;
    }

    t3 = 1.0 / width1;
    t4 = t2 * t3;
    t5 = pow(t4, width2);
    t6 = exp(-t5);
    t8 = amplitude * t5;
    t16 = log(t4);

    *y = amplitude * t6 + background;

    if (dyda != NULL) {

        dyda[LMP_AMPL] = t6;

        dyda[LMP_CENT] = t8 * width2 * t10 / t2 * t6;

        if (isnan(dyda[LMP_CENT])) {
            dyda[LMP_CENT] = 0.0;
        }

        dyda[LMP_BACK] = 1.0;
        dyda[LMP_WID1] = t8 * width2 * t3 * t6;

        dyda[LMP_WID2] = -t8 * t16 * t6;

        if (isnan(dyda[LMP_WID2])) {
            dyda[LMP_WID2] = 0.0;
        }

        if (r != NULL) {

            register cxint k;

            k = LMP_AMPL << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_AMPL] *= _giraffe_dydaweight(a[LMP_AMPL], r[k],
                                                      r[k + 1]);
            }

            k = LMP_CENT << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_CENT] *= _giraffe_dydaweight(a[LMP_CENT], r[k],
                                                      r[k + 1]);
            }

            k = LMP_WID1 << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_WID1] *= _giraffe_dydaweight(a[LMP_WID1], r[k],
                                                      r[k + 1]);
            }

            k = LMP_WID2 << 1;
            if (r[k + 1] > 0) {
                dyda[LMP_WID2] *= _giraffe_dydaweight(a[LMP_WID2], r[k],
                                                      r[k + 1]);
            }

        }

    }

    return;

}


inline static void
_giraffe_test_ctor(GiModel *self, const GiModelData *model)
{

    cx_assert(self != NULL);
    cx_assert(model != NULL);

    self->name = cx_strdup(model->name);
    self->type = model->type;

    self->model = model->eval;


    /*
     * Arguments
     */

    self->arguments.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->arguments.names, "x", 0);

    self->arguments.count = cpl_propertylist_get_size(self->arguments.names);
    self->arguments.values = cpl_matrix_new(self->arguments.count, 1);


    /*
     * Parameters
     */

    self->parameters.names = cpl_propertylist_new();

    cpl_propertylist_append_int(self->parameters.names, "Slope", 0);
    cpl_propertylist_append_int(self->parameters.names, "Intercept", 1);

    self->parameters.count =
        cpl_propertylist_get_size(self->parameters.names);
    self->parameters.values = cpl_matrix_new(self->parameters.count, 1);

    return;

}


inline static void
_giraffe_test_eval(cxdouble *y, cxdouble *x, cxdouble *a, cxint na,
                   cxdouble *dyda, cxdouble *r)
{

    const cxchar *const fctid = "_giraffe_test_eval";


    cxdouble a1;
    cxdouble b1;


    /* Not used */
    (void) r;

    if (na != 2) {
        cpl_error_set(fctid, CPL_ERROR_ILLEGAL_INPUT);
        return;
    }

    *y = 0.0;

    if (dyda != NULL) {
        dyda[0] = 0.;
        dyda[1] = 0.;
    }

    a1 = a[0];
    b1 = a[1];

    *y = a1 * x[0] + b1;

    if (dyda != NULL) {

        dyda[0] = x[0];
        dyda[1] = 0.0;

    }

    return;

}


/*
 * Model registry
 */

static GiModelData _gimodels[] = {
    {"xoptmod", GI_MODEL_XOPT,
     _giraffe_xoptmod_ctor, _giraffe_model_dtor, _giraffe_xoptmod_eval},
    {"yoptmod", GI_MODEL_YOPT,
     _giraffe_yoptmod_ctor, _giraffe_model_dtor, _giraffe_yoptmod_eval},
    {"xoptmod2", GI_MODEL_XOPT,
     _giraffe_xoptmod2_ctor, _giraffe_model_dtor, _giraffe_xoptmod2_eval},
    {"yoptmod2", GI_MODEL_XOPT,
     _giraffe_yoptmod2_ctor, _giraffe_model_dtor, _giraffe_yoptmod2_eval},
    {"gaussian", GI_MODEL_LINE,
     _giraffe_gaussian_ctor, _giraffe_model_dtor, _giraffe_gaussian_eval},
    {"psfcos", GI_MODEL_LINE,
     _giraffe_psfcos_ctor, _giraffe_model_dtor, _giraffe_psfcos_eval},
    {"psfexp", GI_MODEL_LINE,
     _giraffe_psfexp_ctor, _giraffe_model_dtor, _giraffe_psfexp_eval},
    {"psfexp2", GI_MODEL_LINE,
     _giraffe_psfexp2_ctor, _giraffe_model_dtor, _giraffe_psfexp2_eval},
    {"test", GI_MODEL_LINE,
     _giraffe_test_ctor, _giraffe_model_dtor, _giraffe_test_eval},
    {NULL, 0, NULL, NULL, NULL}
};
