#ifndef lint
static char *RCSid = "$Id: fit.c 1.3 1993/08/03 07:14:17 cg Exp cg $";
#endif

/*
 *	Nonlinear least squares fit according to the
 *	Marquardt-Levenberg-algorithm
 *
 *	added as Patch to Gnuplot (v3.2 and higher)
 *	by Carsten Grammes
 *	Experimental Physics, University of Saarbruecken, Germany
 *
 *	Internet address: cagr@rz.uni-sb.de
 *
 *	Copyright of this module:   Carsten Grammes, 1993
 *
 *	Permission to use, copy, and distribute this software and its
 *	documentation for any purpose with or without fee is hereby granted,
 *	provided that the above copyright notice appear in all copies and
 *	that both that copyright notice and this permission notice appear
 *	in supporting documentation.
 *
 *	This software is provided "as is" without express or implied warranty.
 *
 *	930726:     Recoding of the Unix-like raw console I/O routines by:
 *		    Michele Marziani (marziani@ferrara.infn.it)
 */


#define FIT_MAIN

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

#include <string.h>
#include <math.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>

#if defined(MSDOS) || defined(DOS386)	     /* non-blocking IO stuff */
#include <io.h>
#include <conio.h>
#include <dos.h>
#else
#if defined(AMIGA_SC_6_1)
#include <fcntl.h>
#include <ios1.h>
#include <exec/types.h>
#include <exec/tasks.h>
#include <dos/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#else
#include <fcntl.h>
#include <termio.h>
#endif
#endif

#include <setjmp.h>
#define STANDARD    stderr	/* compatible with gnuplot philosophy */

#include "plot.h"
#include "type.h"               /* own types */
#include "matrix.h"
#include "fit.h"

enum marq_res { OK, ERROR, BETTER, WORSE };
typedef enum marq_res marq_res_t;


#define INFINITY    1e30
#define NEARLY_ZERO 1e-30
#define DELTA	    0.001
#define MAX_DATA    2048
#define MAX_PARAMS  32
#define MAX_LAMBDA  1e20
#define MAX_VALUES_PER_LINE	32
#define MAX_VARLEN  32
#define START_LAMBDA 0.01
#if defined(MSDOS) || defined(OS2) || defined(DOS386)
#define PLUSMINUS   "\xF1"                      /* plusminus sign */
#else
#define PLUSMINUS   "+/-"
#endif

static double epsilon	   = 1e-5;		/* convergence criteria */

static char fitfunction[80];
static char fit_script[127];
static char logfile[128]    = "fit.log";
static char *fit_index_def  = "FIT_INDEX";
static char *FIXED	    = "# FIXED";
static char *GNUFITLOG	    = "FIT_LOG";
static char *FITLIMIT	    = "FIT_LIMIT";
static char *FITSCRIPT	    = "FIT_SCRIPT";
static char *DEFAULT_CMD    = "replot";         /* if no fitscript spec. */
static char *TEMPNAME	    = "gfttemp.$$$";


static FILE	    *log_f = NULL;

static word	    num_data,
		    num_params,
		    skip;
static double	    *fit_x,
		    *fit_y,
		    *err_data,
		    *a;

static struct udft_entry func;

typedef char fixstr[MAX_VARLEN+1];
static fixstr	    *par_name;

extern jmp_buf env;			    /* from plot.c */
extern char input_line[];		    /* command.c */

#if !defined(MSDOS) && !defined(DOS386) && !defined(AMIGA_SC_6_1)
static	char buff = 0;	/* Unix, Linux, OS/2: buffer for getch(), kbhit() */
#ifndef OS2
#define getchx	getch	/* Unix or Linux */
#endif
#endif

/*****************************************************************
			 internal Prototypes
*****************************************************************/

static marq_res_t marquardt (double a[], double **alpha, double *chisq,
			     double *lambda, double *varianz);
static boolean analyze (double a[], double **alpha, double beta[],
			   double *chisq, double *varianz);
static void calculate (double *yfunc, double **dyda, double a[]);
static void call_gnuplot (double *par, double *data);
static void show_fit (int i, double chisq, double last_chisq, double *a,
		      double lambda, FILE *device);
static boolean regress (double a[]);
static int scan_num_entries (char *s, double vlist[]);
static boolean is_variable (char *s);
static void copy_max (char *d, char *s, int n);
static double getdvar (char *varname);
static void splitpath (char *s, char *p, char *f);
static void subst (char *s, char from, char to);
static void dbl_fprintf (FILE *f1, FILE *f2, char *s, ...);
static boolean fit_interrupt ();
static boolean is_empty (char *s);


extern struct value *Gcomplex(struct value *v, double re, double im);
extern struct value *Ginteger(struct value *a, int i);
extern double real(struct value *val);
extern void evaluate_at(struct at_type *at_ptr, struct value *val_ptr);
extern int do_line ();

#if !defined(MSDOS) && !defined(DOS386) && !defined(AMIGA_SC_6_1)
/* non-blocking IO stuff */
static int kbhit();
static int getch();
#endif

/*****************************************************************
    in case of fatal errors
*****************************************************************/
void error_ex (char *s, ...)
{
    va_list p;
    char    tmp[128],
	    *sp;

    strcpy (tmp, "          ");         /* start after GNUPLOT> */
    va_start (p, s);
    vsprintf (tmp+9, s, p);
    va_end (p);
    sp = strchr (tmp, '\0');
    while ( *--sp == '\n' )
	;
    strcpy (sp+1, "\n\n");              /* terminate with exactly 2 newlines */
    fprintf (STANDARD, tmp);
    if ( log_f ) {
	fprintf (log_f, "BREAK: %s", tmp);
	fclose (log_f);
	log_f = NULL;
    }
    if ( func.at ) {
	free (func.at); 		    /* release perm. action table */
	func.at = (struct at_type *) NULL;
    }

/* restore raw or cooked */
#if defined(AMIGA_SC_6_1)
    (void) rawcon (0);  /* Since we're returning to the command level, */
                        /* we might as well assume non-raw mode. */
#endif

    longjmp(env, TRUE); 	/* bail out to command line */
}



/*****************************************************************
    fprintf to 2 streams at a time
*****************************************************************/
static void dbl_fprintf (FILE *f1, FILE *f2, char *s, ...)
{
    va_list p;

    va_start (p, s);
    vfprintf (f1, s, p);
    va_start (p, s);
    vfprintf (f2, s, p);
    va_end (p);
}


/*****************************************************************
    Marquardt's nonlinear least squares fit
*****************************************************************/
static marq_res_t marquardt (double a[], double **alpha, double *chisq,
			     double *lambda, double *varianz)
{
    int 	    j;
    static double   *da,	    /* delta-step of the parameter */
		    *temp_a,	    /* temptative new params set   */
		    **one_da,
		    *beta,
		    *tmp_beta,
		    **tmp_alpha,
		    **covar,
		    old_chisq;

    /* Initialization when lambda < 0 */

    if ( *lambda < 0 ) {	/* Get first chi-square check */
	one_da	    = matr (num_params, 1);
	temp_a	    = vec (num_params);
	beta	    = vec (num_params);
	tmp_beta    = vec (num_params);
	da	    = vec (num_params);
	covar	    = matr (num_params, num_params);
	tmp_alpha   = matr (num_params, num_params);

	*lambda  = -*lambda;	  /* make lambda positive */
	return analyze (a, alpha, beta, chisq, varianz) ? OK : ERROR;
    }
    old_chisq = *chisq;

    for ( j=0 ; j<num_params ; j++ ) {
	memcpy (covar[j], alpha[j], num_params*sizeof(double));
	covar[j][j] *= 1 + *lambda;
	one_da[j][0] = beta [j];
    }

    solve (covar, num_params, one_da, 1);	/* Equation solution */

    for ( j=0 ; j<num_params ; j++ )
	da[j] = one_da[j][0];			/* changes in paramss */

    /* once converged, free dynamic allocated vars */

    if ( *lambda == 0.0 ) {
	free (beta);
	free (tmp_beta);
	free (da);
	free (temp_a);
	free_matr (one_da, num_params);
	free_matr (tmp_alpha, num_params);
	free_matr (covar, num_params);
        return OK;
    }

    /* check if trial did ameliorate sum of squares */

    for ( j=0 ; j<num_params ; j++ ) {
	temp_a[j] = a[j] + da[j];
	tmp_beta[j] = beta [j];
	memcpy (tmp_alpha[j], alpha[j], num_params*sizeof(double));
    }

    if ( !analyze (temp_a, tmp_alpha, tmp_beta, chisq, varianz) )
	return ERROR;

    if ( *chisq < old_chisq ) {     /* Success, accept new solution */
	if ( *lambda > 1e-20 )
	    *lambda /= 10;
	old_chisq = *chisq;
	for ( j=0 ; j<num_params ; j++ ) {
	    memcpy (alpha[j], tmp_alpha[j], num_params*sizeof(double));
	    beta[j] = tmp_beta[j];
	    a[j] = temp_a[j];
	}
	return BETTER;
    }
    else {			    /* failure, increase lambda and return */
	*lambda *= 10;
	*chisq = old_chisq;
	return WORSE;
    }
}


/*****************************************************************
    compute chi-square and numeric derivations
*****************************************************************/
static boolean analyze (double a[], double **alpha, double beta[],
			   double *chisq, double *varianz)
{

/*
 *  used by marquardt to evaluate the linearized fitting matrix alpha
 *  and vector beta
 */

    word    k, j, i;
    double  wt, sig2i, dy, **dyda, *yfunc, *edp, *yp, *fp, **dr,
	    **ar, *dc, *bp, *ac;

    yfunc = vec (num_data);
    dyda = matr (num_data, num_params);

    /* initialize alpha beta */
    for ( j=0 ; j<num_params ; j++ ) {
	for ( k=0 ; k<=j ; k++ )
	    alpha[j][k] = 0;
	beta[j] = 0;
    }

    *chisq = *varianz = 0;
    calculate (yfunc, dyda, a);
    edp = err_data;
    yp = fit_y;
    fp = yfunc;
    dr = dyda;

    /* Summation loop over all data */
    for ( i=0 ; i<num_data ; i++, dr++ ) {
	/* The original code read: sig2i = 1/(*edp * *edp++); */
	/* There are some compilers that evaluate the operation */
	/* from right to left, although it is an error to do so. */
	/* Hence the following modification: */
	sig2i = 1/(*edp * *edp);
	edp++;
	dy = *yp++ - *fp++;
	*varianz += dy*dy;
	ar = alpha;
        dc = *dr;
	bp = beta;
	for ( j=0 ; j<num_params ; j++ ) {
	    wt = *dc++ * sig2i;
	    ac = *ar++;
            for ( k=0 ; k<=j ; k++ )
		*ac++ += wt * (*dr)[k];
	    *bp++ += dy * wt;
	}
	*chisq += dy*dy*sig2i;		    /* and find chi-square */
    }

    *varianz /= num_data;
    for ( j=1 ; j<num_params ; j++ )	    /* fill in the symmetric side */
	for ( k=0 ; k<=j-1 ; k++ )
	    alpha[k][j] = alpha [j][k];
    free (yfunc);
    free_matr (dyda, num_data);
    return TRUE;
}


/*****************************************************************
    compute function values and partial derivatives of chi-square
*****************************************************************/
static void calculate (double *yfunc, double **dyda, double a[])
{
    word    k, p;
    double  tmp_a;
    double  *tmp_low,
	    *tmp_high,
	    *tmp_pars;

    tmp_low = vec (num_data);	      /* numeric derivations */
    tmp_high = vec (num_data);
    tmp_pars = vec (num_params);

    /* first function values */

    call_gnuplot (a, yfunc);

    /* then derivatives */

    for ( p=0 ; p<num_params ; p++ )
	tmp_pars[p] = a[p];
    for ( p=0 ; p<num_params ; p++ ) {
	tmp_pars[p] = tmp_a = fabs(a[p]) < NEARLY_ZERO ? NEARLY_ZERO : a[p];
/*
 *  the more exact method costs double execution time and is therefore
 *  commented out here. Change if you like!
 */
/*	  tmp_pars[p] *= 1-DELTA;
	call_gnuplot (tmp_pars, tmp_low);  */

	tmp_pars[p] = tmp_a * (1+DELTA);
	call_gnuplot (tmp_pars, tmp_high);
	for ( k=0 ; k<num_data ; k++ )

/*            dyda[k][p] = (tmp_high[k] - tmp_low[k])/(2*tmp_a*DELTA); */

	    dyda[k][p] = (tmp_high[k] - yfunc[k])/(tmp_a*DELTA);
        tmp_pars[p] = a[p];
    }

    free (tmp_low);
    free (tmp_high);
    free (tmp_pars);
}


/*****************************************************************
    call internal gnuplot functions
*****************************************************************/
static void call_gnuplot (double *par, double *data)
{
    word    i;
    struct value v;

    extern struct at_type *perm_at();
    extern int scanner (char *);

    /* all in command.c */
    extern char input_line[];
    extern int num_tokens, c_token;
    extern char dummy_var[MAX_NUM_VAR][MAX_ID_LEN+1];
    extern char c_dummy_var[MAX_NUM_VAR][MAX_ID_LEN+1];
    extern struct udft_entry *dummy_func;

    /* set parameters first */
    for ( i=0 ; i<num_params ; i++ ) {
	Gcomplex (&v, par[i], 0.0);
	setvar (par_name[i], v);
    }

    (void) strcpy (c_dummy_var[0], dummy_var[0]);

    if ( !func.at ) {		    /* parse fitfunction, build action table */
	strcpy (input_line, fitfunction);
	num_tokens = scanner (input_line);	/* divide into tokens */
	c_token = 0;
	dummy_func = &func;
	func.at = perm_at();			/* parse function */
    }
    for (i = 0; i < MAX_NUM_VAR; i++)
	c_dummy_var[i][0] = '\0';               /* no dummy variables */

    dummy_func = &func; 			/* by caution */
    for (i = 0; i < num_data; i++) {
	/* fit_index for multi-branch functions */
	(void) Ginteger (&func.dummy_values[0], (i+1)*skip);
	setvar (fit_index, func.dummy_values[0]);
	/* calculate fit-function value */
	(void) Gcomplex (&func.dummy_values[0], fit_x[i], 0.0);
	evaluate_at (func.at,&v);
	data[i] = real(&v);
    }
    (void) Ginteger (&func.dummy_values[0], 0);
    setvar (fit_index, func.dummy_values[0]);
}


/*****************************************************************
    getch that handles also function keys etc.
*****************************************************************/
#if defined(MSDOS) || defined(OS2) || defined(DOS386)
			     /* if not MSDOS or OS/2, getchx not used*/
int getchx ()
{
    register int c = getch();
    if ( !c  ||  c == 0xE0 ) {
	c <<= 8;
	c |= getch ();
    }
    return c;
}
#endif


/*****************************************************************
    handle user interrupts during fit
*****************************************************************/
static boolean fit_interrupt ()
{
    int  c;

    while ( TRUE ) {
	fprintf (STANDARD, "\n\n(S)top fit, (C)ontinue, (E)xecute:  ");
#if defined(AMIGA_SC_6_1)
	c = getch ();
#else /* !AMIGA_SC_6_1 */
	do {c = getchx ();} while ( kbhit() );
#endif /* !AMIGA_SC_6_1 */
        switch ( toupper (c) ) {
	case 'S' :  fprintf (STANDARD, "Stop.");
		    return FALSE;
	case 'C' :  fprintf (STANDARD, "Continue.");
		    return TRUE;
	case 'E' :  {
			int i;
			struct value v;
			char *tmp;

			tmp = *fit_script ? fit_script : DEFAULT_CMD;
			fprintf (STANDARD, "executing: %s", tmp);
			/* set parameters visible to gnuplot */
			for ( i=0 ; i<num_params ; i++ ) {
			    Gcomplex (&v, a[i], 0.0);
			    setvar (par_name[i], v);
			}
			sprintf (input_line, tmp);
			(void) do_line ();
		    }
	}
    }
}


/*****************************************************************
    frame routine for the marquardt-fit
*****************************************************************/
static boolean regress (double a[])
{
    double	**covar,
		**correl,
		*dpar,
		**alpha,
		chisq,
		last_chisq,
		varianz,
		lambda;
    word	i, j;
    int 	c;
    marq_res_t	res;
    struct value val;
#if defined(AMIGA_SC_6_1)
    BPTR StdinHandle;
#endif

    chisq   = last_chisq = INFINITY;
    alpha   = matr (num_params, num_params);
    lambda  = -START_LAMBDA;			/* use sign as flag */
    i = 0;					/* iteration counter  */

    /* Initialize internal variables and 1st chi-square check */
    if ( (res = marquardt (a, alpha, &chisq, &lambda, &varianz)) == ERROR )
	error_ex ("FIT: error occured during fit");
    res = BETTER;

    dbl_fprintf (STANDARD, log_f, "\nInitial set of free parameters:\n");
    show_fit (i, chisq, chisq, a, lambda, STANDARD);
    show_fit (i, chisq, chisq, a, lambda, log_f);

    /* MAIN FIT LOOP: do the regression iteration */

    do {
#if defined(AMIGA_SC_6_1)
	StdinHandle = chkufb (fileno (stdin))->ufbfh;
	if (IsInteractive (StdinHandle) && 
	    WaitForChar (StdinHandle, 0) == DOSTRUE) {
	    fread (&c, 1, 1, stdin);
#else /* !AMIGA_SC_6_1 */
	if ( kbhit () ) {
	    do { getchx (); } while ( kbhit() );
#endif /* !AMIGA_SC_6_1 */
	    show_fit (i, chisq, last_chisq, a, lambda, STANDARD);
	    if ( !fit_interrupt () )	       /* handle keys */
		break;
	}
	if ( res == BETTER ) {
            i++;
            last_chisq = chisq;
	}
        if ( (res = marquardt (a, alpha, &chisq, &lambda, &varianz)) == BETTER )
	    show_fit (i, chisq, last_chisq, a, lambda, STANDARD);
    }
    while ( res != ERROR  &&  lambda < MAX_LAMBDA  &&
	    (res == WORSE  ||  (last_chisq-chisq)/chisq > epsilon) );

    dbl_fprintf (STANDARD, log_f, "\nAfter %d iterations the fit converged.\n", i);
    dbl_fprintf (STANDARD, log_f, "final sum of squares residuals    : %g\n", chisq);
    dbl_fprintf (STANDARD, log_f, "rel. change during last iteration : %g\n\n", (chisq-last_chisq)/chisq);

    if ( res == ERROR )
	error_ex ("FIT: error occured during fit");

    /* compute errors in the parameters */

    for ( i=0 ; i<num_params; i++ )
	for ( j=0 ; j<num_params; j++ )
	    alpha[i][j] /= varianz;

    /* get covariance-, Korrelations- and Kurvature-Matrix */
    /* and errors in the parameters			  */

    covar   = matr (num_params, num_params);
    correl  = matr (num_params, num_params);
    dpar = vec (num_params);

    inverse (alpha, covar, num_params);

    dbl_fprintf (STANDARD, log_f, "Final set of parameters            68.3%% confidence interval (one at a time)\n");
    dbl_fprintf (STANDARD, log_f, "=======================            =========================================\n\n");
    for ( i=0 ; i<num_params; i++ ) {
	dpar[i] = sqrt (covar[i][i]);
	dbl_fprintf (STANDARD, log_f, "%-15.15s = %-15g  %-3.3s %g\n",
            par_name[i], a[i], PLUSMINUS, dpar[i]);
    }

    dbl_fprintf (STANDARD, log_f, "\n\ncorrelation matrix of the fit parameters:\n\n");
    dbl_fprintf (STANDARD, log_f, "               ");
    for ( j=0 ; j<num_params ; j++ )
	dbl_fprintf (STANDARD, log_f, "%-6.6s ", par_name[j]);
    dbl_fprintf (STANDARD, log_f, "\n");

    for ( i=0 ; i<num_params; i++ ) {
	dbl_fprintf (STANDARD, log_f, "%-15.15s", par_name[i]);
	for ( j=0 ; j<num_params; j++ ) {
	    correl[i][j] = covar[i][j] / (dpar[i]*dpar[j]);
	    dbl_fprintf (STANDARD, log_f, "%6.3f ", correl[i][j]);
        }
	dbl_fprintf (STANDARD, log_f, "\n");
    }

    /* restore last parameter's value (not done by calculate) */
    Gcomplex (&val, a[num_params-1], 0.0);
    setvar (par_name[num_params-1], val);

    /* call destruktor for allocated vars */
    lambda = 0;
    marquardt (a, alpha, &chisq, &lambda, &varianz);

    free_matr (covar, num_params);
    free_matr (alpha, num_params);
    free_matr (correl, num_params);
    free (dpar);

    return TRUE;
}


/*****************************************************************
    display actual state of the fit
*****************************************************************/
static void show_fit (int i, double chisq, double last_chisq, double *a,
		      double lambda, FILE *device)
{
    int k;

    fprintf (device, "\n\n\
Iteration %d\n\
chisquare : %-15g   relative deltachi2 : %g\n\
deltachi2 : %-15g   limit for stopping : %g\n\
lambda	  : %g\n\nactual set of parameters\n\n",
i, chisq, (chisq - last_chisq)/chisq, chisq - last_chisq, epsilon, lambda);
    for ( k=0 ; k<num_params ; k++ )
	fprintf (device, "%-15.15s = %g\n", par_name[k], a[k]);
}



/*****************************************************************
    is_empty: check for valid string entries
*****************************************************************/
static boolean is_empty (char *s)
{
    while ( *s == ' '  ||  *s == '\t'  ||  *s == '\n' )
	s++;
    return ( *s == '#'  ||  *s == '\0' );
}


/*****************************************************************
    get next word of a multi-word string, advance pointer
*****************************************************************/
char *get_next_word (char **s, char *subst)
{
    char *tmp = *s;

    while ( *tmp==' ' || *tmp=='\t' || *tmp=='=' )
	tmp++;
    if ( *tmp=='\n' || *tmp=='\0' )                    /* not found */
	return NULL;
    if ( (*s = strpbrk (tmp, " =\t\n")) == NULL )
	*s = tmp + strlen(tmp);
    *subst = **s;
    *(*s)++ = '\0';
    return tmp;
}



/*****************************************************************
    get valid numerical entries
*****************************************************************/
static int scan_num_entries (char *s, double vlist[])
{
    int num = 0;
    char c, *tmp;

    while ( (tmp = get_next_word (&s, &c)) != NULL
					&&  num <= MAX_VALUES_PER_LINE )
	if ( !sscanf (tmp, "%lg", &vlist[++num]) )
	    error_ex ("syntax error in data file");
    return num;
}


/*****************************************************************
    check for variable identifiers
*****************************************************************/
static boolean is_variable (char *s)
{
    while ( *s ) {
	if ( !isalnum(*s) && *s!='_' )
	    return FALSE;
	s++;
    }
    return TRUE;
}


/*****************************************************************
    strcpy but max n chars
*****************************************************************/
static void copy_max (char *d, char *s, int n)
{
    strncpy (d, s, n);
    if ( strlen(s) >= n )
	d[n-1] = '\0';
}


/*****************************************************************
    Slow but portable implementation of strnicmp
*****************************************************************/
/* 
 * please don't add further machine-specific macros, use -DSTRNICMP
 * as compiler switch if necessary
 */

#ifdef STRNICMP
int strnicmp (char *s1, char *s2, int n)
{
    static char ss1[256], ss2[256];
    register char *p;

    copy_max (ss1, s1, sizeof(ss1));
    copy_max (ss2, s2, sizeof(ss2));
    p = ss1-1;
    while ( *++p )
	toupper ( *p );
    p = ss2-1;
    while ( *++p )
        toupper ( *p );
    return strncmp (ss1, ss2, n);
}
#endif


/*****************************************************************
    first time settings
*****************************************************************/
void init_fit ()
{
    struct value val;

    /* index used for multi-branch functions */
    fit_index = fit_index_def;
    val.type = INTGR;
    val.v.int_val = 0;
    setvar (fit_index, val);
    func.at = (struct at_type *) NULL;      /* need to parse 1 time */
}


/*****************************************************************
	    Set a GNUPLOT user-defined variable
******************************************************************/
extern struct udvt_entry *first_udv;

void setvar (char *varname, struct value data)
{
    register struct udvt_entry *udv_ptr = first_udv,
			       *last = first_udv;

    /* check if it's already in the table... */

    while (udv_ptr) {
	    last = udv_ptr;
	    if ( !strcmp (varname, udv_ptr->udv_name))
		break;
	    udv_ptr = udv_ptr->next_udv;
    }

    if ( !udv_ptr ) {			    /* generate new entry */
	last->next_udv = udv_ptr = (struct udvt_entry *)
	  alloc ((unsigned int)sizeof(struct udvt_entry), "setvar");
	udv_ptr->next_udv = NULL;
    }
    copy_max (udv_ptr->udv_name, varname, sizeof(udv_ptr->udv_name));
    udv_ptr->udv_value = data;
    udv_ptr->udv_undef = FALSE;
}



/*****************************************************************
    Read INTGR Variable value, return 0 if undefined or wrong type
*****************************************************************/
int getivar (char *varname)
{
    register struct udvt_entry *udv_ptr = first_udv;

    while (udv_ptr) {
	    if ( !strcmp (varname, udv_ptr->udv_name))
		return udv_ptr->udv_value.type == INTGR
		       ? udv_ptr->udv_value.v.int_val	/* valid */
		       : 0;				/* wrong type */
            udv_ptr = udv_ptr->next_udv;
    }
    return 0;						/* not in table */
}


/*****************************************************************
    Read DOUBLE Variable value, return 0 if undefined or wrong type
*****************************************************************/
static double getdvar (char *varname)
{
    register struct udvt_entry *udv_ptr = first_udv;

    while (udv_ptr) {
	    if ( !strcmp (varname, udv_ptr->udv_name))
		return udv_ptr->udv_value.type == CMPLX
		       ? udv_ptr->udv_value.v.cmplx_val.real	/* valid */
		       : 0;				   /* wrong type */
            udv_ptr = udv_ptr->next_udv;
    }
    return 0;						 /* not in table */
}


/*****************************************************************
    Split Identifier into path and filename
*****************************************************************/
static void splitpath (char *s, char *p, char *f)
{
    register char *tmp;
    tmp = s + strlen(s) - 1;
    while ( *tmp != '\\'  &&  *tmp != '/'  &&  *tmp != ':'  &&  tmp-s >= 0 )
	tmp--;
    strcpy (f, tmp+1);
    strncpy (p, s, tmp-s+1);
    p[tmp-s+1] = '\0';
}


/*****************************************************************
    Character substitution
*****************************************************************/
static void subst (char *s, char from, char to)
{
    while ( *s ) {
	if ( *s == from )
	    *s = to;
	s++;
    }
}


/*****************************************************************
    write the actual parameters to start parameter file
*****************************************************************/
void update (char *pfile)
{
    char	fnam[256],
		path[256],
		tmpnam[256],
		sstr[256],
		pname[64],
		tail[127],
		*s = sstr,
#if !defined(AMIGA_SC_6_1)
		*tmpn,
#endif
		*tmp,
		c;
    FILE        *of,
		*nf;
    double	pval;

    /* split into path and filename */

#if defined(MSDOS) || defined(DOS386)
    /* don't get problems during system() call */
    subst (pfile, '/', '\\');
#endif
    splitpath (pfile, path, fnam);
    if ( !(of = fopen (pfile, "r")) )
	error_ex ("parameter file %s could not be read", pfile);
#if defined(AMIGA_SC_6_1)
    sprintf (tmpnam, "%sgf%08lx", path, FindTask (NULL));
#else /* !AMIGA_SC_6_1 */
    /* Under MSDOS rename will not work properly if TMP points to another
     * drive/dir. In this case use 'path' directory for tempfile
     */
#if defined (MSDOS)  ||  defined (OS2)
    sprintf (tmpnam, "%s%s", path, TEMPNAME);
#else
    tmpn = tempnam (path, "gnuft");
    if ( tmpn == NULL )
        error_ex ("no more unique names possible during updating");
    strcpy (tmpnam, tmpn);
    free (tmpn);
#endif
#endif /* !AMIGA_SC_6_1 */
    if ( !(nf = fopen (tmpnam, "w")) )
	error_ex ("could not open temporary file");
    while ( TRUE ) {
	if ( !fgets (s = sstr, sizeof(sstr), of) )	/* EOF found */
	    break;
	if ( is_empty(s) ) {
	    fprintf (nf, s);				/* preserve comments */
            continue;
        }
        if ( tmp = strchr (s, '#') ) {
	    copy_max (tail, tmp, sizeof(tail));
	    *tmp = '\0';
	}
	else
	    strcpy (tail, "\n");
	tmp = get_next_word (&s, &c);
	if ( !is_variable (tmp)  ||  strlen(tmp) > MAX_VARLEN ) {
	    fclose (of);
	    error_ex ("syntax error in parameter file %s", fnam);
	}
	copy_max (pname, tmp, sizeof(pname));
	/* next must be '=' */
	if ( c != '=' ) {
	    tmp = strchr (s, '=');
	    if ( tmp == NULL ) {
		fclose (of);
		error_ex ("syntax error in parameter file %s", fnam);
	    }
	    s = tmp+1;
	}
	tmp = get_next_word (&s, &c);
	if ( !sscanf (tmp, "%lg", &pval) ) {
	    fclose (of);
	    error_ex ("syntax error in parameter file %s", fnam);
	}
	if ( (tmp = get_next_word (&s, &c)) != NULL ) {
	    fclose (of);
	    error_ex ("syntax error in parameter file %s", fnam);
	}

        /* now modify */

	if ( (pval = getdvar (pname)) == 0 )
	    pval = (double) getivar (pname);

	sprintf (sstr, "%g", pval);
	if ( !strchr (sstr, '.')  &&  !strchr (sstr, 'e') )
	    strcat (sstr, ".0");                /* assure CMPLX-type */
	fprintf (nf, "%-15.15s = %-15.15s   %s", pname, sstr, tail);
    }
    if ( fclose (of)  ||  fclose (nf) )
	error_ex ("I/O error during update");

    if ( unlink (pfile) )     /* implies write permission !!!! */
      error_ex ("parameter file %s could not be deleted", pfile);

#if defined(MSDOS) || defined(OS2)
    sprintf (sstr, "ren %s %s", tmpnam, fnam);
    if ( system (sstr) )
#else                   /* implies write permission !!!! */
    if ( rename (tmpnam, pfile) )
#endif
      error_ex ("unable to rename %s to %s", tmpnam, pfile);
}



/*****************************************************************
    Interface to the classic gnuplot-software
*****************************************************************/
void do_fit (char *fitfunc, char *datafile, int xcol, int ycol, int dycol,
	     char *paramsfile)
{
    FILE	*f;
    int 	i, num, num_rawdata;
    static char sstr[1024];
    char	c,
		*s = sstr,
		*tmp;
    double	vlist[MAX_VALUES_PER_LINE],
		tmpd;
    time_t	timer;


/* Amiga: force terminal to raw mode to handle user interrupts */
#if defined(AMIGA_SC_6_1)
    (void) rawcon (1);
#endif

    strcpy (fitfunction, fitfunc);
    if ( !(f = fopen (datafile, "r")) )
	error_ex ("datafile %s could not be read", datafile);

    tmpd = getdvar (FITLIMIT);		  /* get epsilon if given explicitly */
    if ( tmpd < 1.0  &&  tmpd > 0.0 )
        epsilon = tmpd;

    *fit_script = '\0';                   /* FIT_SKIP 1 means each 2nd value */
    if ( tmp = getenv (FITSCRIPT) )
	strcpy (fit_script, tmp);
    skip = 1 + (word) getivar (FIT_SKIP);

    tmp = getenv (GNUFITLOG);		  /* open logfile */
    if ( tmp != NULL ) {
        char *tmp2 = &tmp[strlen(tmp)-1];
        if ( *tmp2 == '/'  ||  *tmp2 == '\\' )
            sprintf (logfile, "%s%s", tmp, logfile);
        else
            strcpy (logfile, tmp);
    }
    if ( !(log_f = fopen (logfile, "a")) )
        error_ex ("could not open log-file %s", logfile);
    fprintf (log_f, "\n\n*******************************************************************************\n");
    time (&timer);
    fprintf (log_f, "%s\n\n", ctime (&timer));
    fprintf (log_f, "FIT:    data from %s\n        x = column #%d\n        y = column #%d\n",
                datafile, xcol, ycol);

    fit_x = vec (MAX_DATA);		 /* start with max. value */
    fit_y = vec (MAX_DATA);

    /* first read in experimental data */

    err_data = vec (MAX_DATA);
    num_rawdata = num_data = 0;
    while ( TRUE ) {
	if ( ! fgets (s = sstr, sizeof(sstr), f) )
	    break;				    /* EOF found */
	if ( is_empty(s) )
	    continue;
	if ( num_data==MAX_DATA ) {
	    fclose (f);
	    error_ex ("max. # of datapoints %d exceeded", MAX_DATA);
	}
	/* expecting n value entries (x, y (, yerror) ) */
	num = scan_num_entries (s, vlist);
	if ( xcol>num || ycol>num || dycol>num ) {
	    fclose (f);
	    error_ex ("columns in %s do not match specification", datafile);
	}
	if ( ++num_rawdata % skip )		    /* ignore this one */
	    continue;
	fit_x[num_data] = vlist[xcol];
	fit_y[num_data]   = vlist[ycol];
	err_data[num_data++] = dycol ? vlist[dycol] : 1;
    }
    fclose (f);
    /* now resize fields */
    redim_vec (&fit_x, num_data);
    redim_vec (&fit_y, num_data);
    redim_vec (&err_data, num_data);
    
    fprintf (log_f, "        #datapoints = %d", num_rawdata);
    if ( skip > 1 )
	fprintf (log_f, "   use each %d. value\n", skip);
    else
	fprintf (log_f, "\n");
    if ( dycol )
	fprintf (log_f, "        y-error = column #%d\n\n", dycol);
    else
	fprintf (log_f, "        y-errors assumed equally distributed\n\n");
    fprintf (log_f, "function used for fitting: %s\n", fitfunction);

    /* read in parameters */

    a = vec (MAX_PARAMS);
    if ((par_name = (fixstr *) malloc ((MAX_PARAMS+1)*sizeof(fixstr))) == NULL)
	error_ex ("Memory running low during fit");
    num_params = 0;

    if ( !strnicmp (paramsfile, "via ", 4) ) {   /* no values given */
	char *tmp2;

	fprintf (log_f, "use actual parameter values\n\n");
	paramsfile += 4;
	do {
	    tmp = strchr (paramsfile, ',');
	    if ( tmp != NULL )
		*tmp = '\0';
	    tmp2 = get_next_word (&paramsfile, &c);
	    if ( tmp2 == NULL )
		error_ex ("no parameter specified");
	    copy_max (par_name[num_params], tmp2, sizeof(fixstr));
	    a[num_params] = getdvar (par_name[num_params]);
	    num_params++;
	    paramsfile = tmp+1;
	}
	while ( tmp );
    }
    else {
	boolean fixed;
	double	tmp_par;

	/* get parameters and values out of file and ignore fixed ones */

	fprintf (log_f, "take parameters from file: %s\n\n", paramsfile);
	if ( !(f = fopen (paramsfile, "r")) )
	    error_ex ("could not read parameter-file %s", paramsfile);
	while ( TRUE ) {
	    if ( !fgets (s = sstr, sizeof(sstr), f) )		/* EOF found */
		break;
	    if ( tmp = strstr (s, FIXED) ) {	      /* ignore fixed params */
		*tmp = '\0';
		fprintf (log_f, "FIXED:  %s\n", s);
		fixed = TRUE;
	    }
	    else
		fixed = FALSE;
            if ( tmp = strchr (s, '#') )
		*tmp = '\0';
	    if ( is_empty(s) )
		continue;
	    tmp = get_next_word (&s, &c);
	    if ( !is_variable (tmp)  ||  strlen(tmp) > MAX_VARLEN ) {
		fclose (f);
		error_ex ("syntax error in parameter file %s", paramsfile);
	    }
	    copy_max (par_name[num_params], tmp, sizeof(fixstr));
	    /* next must be '=' */
	    if ( c != '=' ) {
		tmp = strchr (s, '=');
		if ( tmp == NULL ) {
		    fclose (f);
		    error_ex ("syntax error in parameter file %s", paramsfile);
		}
		s = tmp+1;
	    }
	    tmp = get_next_word (&s, &c);
	    if ( !sscanf (tmp, "%lg", &tmp_par) ) {
		fclose (f);
		error_ex ("syntax error in parameter file %s", paramsfile);
	    }

	    /* make fixed params visible to GNUPLOT */
	    if ( fixed ) {
		struct value v;
		Gcomplex (&v, tmp_par, 0.0);
		setvar (par_name[num_params], v);   /* use parname as temp */
	    }
	    else
		a[num_params++] = tmp_par;

	    if ( (tmp = get_next_word (&s, &c)) != NULL ) {
		fclose (f);
		error_ex ("syntax error in parameter file %s", paramsfile);
	    }
	}
	fclose (f);
    }
    redim_vec (&a, num_params);
    par_name = (fixstr *) realloc (par_name, (num_params+1)*sizeof(fixstr));

    /* avoid parameters being equal to zero */
    for ( i=0 ; i<num_params ; i++ )
	if ( a[i] == 0 )
	    a[i] = NEARLY_ZERO;

    regress (a);

    fclose (log_f);
    log_f = NULL;
    free (fit_x);
    free (fit_y);
    free (err_data);
    free (a);
    free (par_name);
    if ( func.at ) {
        free (func.at);                     /* release perm. action table */
        func.at = (struct at_type *) NULL;
    }

/* restore raw or cooked */
#if defined(AMIGA_SC_6_1)
    (void) rawcon (0);
#endif
}


/*****************************************************************
    replace for kbhit() under UNIXish systems
*****************************************************************/
#if !defined(MSDOS) && !defined(DOS386) && !defined(AMIGA_SC_6_1)
int	kbhit()
{
   int r;
   struct termio tmo, tmn;

   if (buff)
      return 1;

   ioctl(fileno(stdin),TCGETA,&tmo);
   tmn = tmo;
   tmn.c_iflag = tmn.c_lflag = 0;
   tmn.c_cc[VMIN]  = 0;      /* minimum number of characters to be read */
   tmn.c_cc[VTIME] = 1;      /* time-out in tenths of a second */
   ioctl(fileno(stdin),TCSETA,&tmn);

   r = read(fileno(stdin),&buff,1);
   ioctl(fileno(stdin),TCSETA,&tmo);
   return (r > 0);
}

int	getch()
{
   char c;
   struct termio tmo, tmn;

   if (buff) {
      c = buff;
      buff = 0;
      return (c & 127);
   }

   ioctl(fileno(stdin),TCGETA,&tmo);
   tmn = tmo;
   tmn.c_iflag = tmn.c_lflag = 0;
   tmn.c_cc[VMIN]  = 1;      /* minimum number of characters to be read */
   tmn.c_cc[VTIME] = 0;      /* time-out in tenths of a second */
   ioctl(fileno(stdin),TCSETA,&tmn);

   while (read(fileno(stdin),&c,1) != 1)
     ;	/* do nothing, just wait */
   ioctl(fileno(stdin),TCSETA,&tmo);
   return (c & 127);
}
#endif

