Initial release of Maemo 5 port of gnuplot
[gnuplot] / src / fit.c
diff --git a/src/fit.c b/src/fit.c
new file mode 100644 (file)
index 0000000..a420a22
--- /dev/null
+++ b/src/fit.c
@@ -0,0 +1,1736 @@
+#ifndef lint
+static char *RCSid() { return RCSid("$Id: fit.c,v 1.56.2.4 2009/02/05 17:17:25 sfeam Exp $"); }
+#endif
+
+/*  NOTICE: Change of Copyright Status
+ *
+ *  The author of this module, Carsten Grammes, has expressed in
+ *  personal email that he has no more interest in this code, and
+ *  doesn't claim any copyright. He has agreed to put this module
+ *  into the public domain.
+ *
+ *  Lars Hecking  15-02-1999
+ */
+
+/*
+ *    Nonlinear least squares fit according to the
+ *      Marquardt-Levenberg-algorithm
+ *
+ *      added as Patch to Gnuplot (v3.2 and higher)
+ *      by Carsten Grammes
+ *
+ *      930726:     Recoding of the Unix-like raw console I/O routines by:
+ *                  Michele Marziani (marziani@ferrara.infn.it)
+ * drd: start unitialised variables at 1 rather than NEARLY_ZERO
+ *  (fit is more likely to converge if started from 1 than 1e-30 ?)
+ *
+ * HBB (broeker@physik.rwth-aachen.de) : fit didn't calculate the errors
+ * in the 'physically correct' (:-) way, if a third data column containing
+ * the errors (or 'uncertainties') of the input data was given. I think
+ * I've fixed that, but I'm not sure I really understood the M-L-algo well
+ * enough to get it right. I deduced my change from the final steps of the
+ * equivalent algorithm for the linear case, which is much easier to
+ * understand. (I also made some minor, mostly cosmetic changes)
+ *
+ * HBB (again): added error checking for negative covar[i][i] values and
+ * for too many parameters being specified.
+ *
+ * drd: allow 3d fitting. Data value is now called fit_z internally,
+ * ie a 2d fit is z vs x, and a 3d fit is z vs x and y.
+ *
+ * Lars Hecking : review update command, for VMS in particular, where
+ * it is not necessary to rename the old file.
+ *
+ * HBB, 971023: lifted fixed limit on number of datapoints, and number
+ * of parameters.
+ */
+
+#include "fit.h"
+
+#include <signal.h>
+
+#include "alloc.h"
+#include "axis.h"
+#include "command.h"
+#include "datafile.h"
+#include "eval.h"
+#include "gp_time.h"
+#include "matrix.h"
+#include "plot.h"
+#include "misc.h"
+#include "util.h"
+
+/* Just temporary */
+#if defined(VA_START) && defined(STDC_HEADERS)
+static void Dblfn __PROTO((const char *fmt, ...));
+#else
+static void Dblfn __PROTO(());
+#endif
+#define Dblf  Dblfn
+#define Dblf2 Dblfn
+#define Dblf3 Dblfn
+#define Dblf5 Dblfn
+#define Dblf6 Dblfn
+
+#if defined(MSDOS) || defined(DOS386)  /* non-blocking IO stuff */
+# include <io.h>
+# ifndef _Windows              /* WIN16 does define MSDOS .... */
+#  include <conio.h>
+# endif
+# include <dos.h>
+#else /* !(MSDOS || DOS386) */
+# ifndef VMS
+#  include <fcntl.h>
+# endif                                /* !VMS */
+#endif /* !(MSDOS || DOS386) */
+
+#if defined(ATARI) || defined(MTOS)
+# define getchx() Crawcin()
+static int kbhit(void);
+#endif
+
+enum marq_res {
+    OK, ML_ERROR, BETTER, WORSE
+};
+typedef enum marq_res marq_res_t;
+
+#ifdef INFINITY
+# undef INFINITY
+#endif
+
+#define INFINITY    1e30
+#define NEARLY_ZERO 1e-30
+
+/* create new variables with this value (was NEARLY_ZERO) */
+#define INITIAL_VALUE 1.0
+
+/* Relative change for derivatives */
+#define DELTA      0.001
+
+#define MAX_DATA    2048
+#define MAX_PARAMS  32
+#define MAX_LAMBDA  1e20
+#define MIN_LAMBDA  1e-20
+#define LAMBDA_UP_FACTOR 10
+#define LAMBDA_DOWN_FACTOR 10
+
+#if defined(MSDOS) || defined(OS2) || defined(DOS386)
+# define PLUSMINUS   "\xF1"    /* plusminus sign */
+#else
+# define PLUSMINUS   "+/-"
+#endif
+
+#define LASTFITCMDLENGTH 511
+
+/* externally visible variables: */
+
+/* saved copy of last 'fit' command -- for output by "save" */
+char fitbuf[256];
+
+/* log-file for fit command */
+char *fitlogfile = NULL;
+
+#ifdef GP_FIT_ERRVARS
+TBOOLEAN fit_errorvariables = FALSE;
+#endif /* GP_FIT_ERRVARS */
+
+/* private variables: */
+
+static int max_data;
+static int max_params;
+
+static double epsilon = 1e-5;  /* convergence limit */
+static int maxiter = 0;
+
+static char fit_script[256];
+
+/* HBB/H.Harders 20020927: log file name now changeable from inside
+ * gnuplot */
+static const char fitlogfile_default[] = "fit.log";
+static const char GNUFITLOG[] = "FIT_LOG";
+
+static const char *GP_FIXED = "# FIXED";
+static const char *FITLIMIT = "FIT_LIMIT";
+static const char *FITSTARTLAMBDA = "FIT_START_LAMBDA";
+static const char *FITLAMBDAFACTOR = "FIT_LAMBDA_FACTOR";
+static const char *FITMAXITER = "FIT_MAXITER"; /* HBB 970304: maxiter patch */
+static const char *FITSCRIPT = "FIT_SCRIPT";
+static const char *DEFAULT_CMD = "replot";     /* if no fitscript spec. */
+static char last_fit_command[LASTFITCMDLENGTH+1] = "";
+
+static FILE *log_f = NULL;
+
+static int num_data, num_params;
+static int columns;
+static double *fit_x = 0, *fit_y = 0, *fit_z = 0, *err_data = 0, *a = 0;
+static TBOOLEAN ctrlc_flag = FALSE;
+static TBOOLEAN user_stop = FALSE;
+
+static struct udft_entry func;
+
+typedef char fixstr[MAX_ID_LEN+1];
+
+static fixstr *par_name;
+
+static double startup_lambda = 0, lambda_down_factor = LAMBDA_DOWN_FACTOR, lambda_up_factor = LAMBDA_UP_FACTOR;
+
+/*****************************************************************
+                        internal Prototypes
+*****************************************************************/
+
+static RETSIGTYPE ctrlc_handle __PROTO((int an_int));
+static void ctrlc_setup __PROTO((void));
+static marq_res_t marquardt __PROTO((double a[], double **alpha, double *chisq,
+                                    double *lambda));
+static TBOOLEAN analyze __PROTO((double a[], double **alpha, double beta[],
+                                double *chisq));
+static void calculate __PROTO((double *zfunc, double **dzda, double a[]));
+static void call_gnuplot __PROTO((double *par, double *data));
+static TBOOLEAN fit_interrupt __PROTO((void));
+static TBOOLEAN regress __PROTO((double a[]));
+static void show_fit __PROTO((int i, double chisq, double last_chisq, double *a,
+                             double lambda, FILE * device));
+static void log_axis_restriction __PROTO((FILE *log_f, AXIS_INDEX axis));
+static TBOOLEAN is_empty __PROTO((char *s));
+static TBOOLEAN is_variable __PROTO((char *s));
+static double getdvar __PROTO((const char *varname));
+static int getivar __PROTO((const char *varname));
+static void setvar __PROTO((char *varname, struct value data));
+static char *get_next_word __PROTO((char **s, char *subst));
+static double createdvar __PROTO((char *varname, double value));
+static void splitpath __PROTO((char *s, char *p, char *f));
+static void backup_file __PROTO((char *, const char *));
+
+#ifdef GP_FIT_ERRVARS
+static void setvarerr __PROTO((char *varname, double value));
+#endif
+
+/*****************************************************************
+    Small function to write the last fit command into a file
+    Arg: Pointer to the file; if NULL, nothing is written,
+         but only the size of the string is returned.
+*****************************************************************/
+
+size_t
+wri_to_fil_last_fit_cmd(FILE *fp)
+{
+    if (fp == NULL)
+       return strlen(last_fit_command);
+    else
+       return (size_t) fputs(last_fit_command, fp);
+}
+
+
+/*****************************************************************
+    This is called when a SIGINT occurs during fit
+*****************************************************************/
+
+static RETSIGTYPE
+ctrlc_handle(int an_int)
+{
+    (void) an_int;             /* avoid -Wunused warning */
+    /* reinstall signal handler (necessary on SysV) */
+    (void) signal(SIGINT, (sigfunc) ctrlc_handle);
+    ctrlc_flag = TRUE;
+}
+
+
+/*****************************************************************
+    setup the ctrl_c signal handler
+*****************************************************************/
+static void
+ctrlc_setup()
+{
+/*
+ *  MSDOS defines signal(SIGINT) but doesn't handle it through
+ *  real interrupts. So there remain cases in which a ctrl-c may
+ *  be uncaught by signal. We must use kbhit() instead that really
+ *  serves the keyboard interrupt (or write an own interrupt func
+ *  which also generates #ifdefs)
+ *
+ *  I hope that other OSes do it better, if not... add #ifdefs :-(
+ */
+#if (defined(__EMX__) || !defined(MSDOS) && !defined(DOS386)) && !defined(ATARI) && !defined(MTOS)
+    (void) signal(SIGINT, (sigfunc) ctrlc_handle);
+#endif
+}
+
+
+/*****************************************************************
+    getch that handles also function keys etc.
+*****************************************************************/
+#if defined(MSDOS) || defined(DOS386)
+
+/* HBB 980317: added a prototype... */
+int getchx __PROTO((void));
+
+int
+getchx()
+{
+    int c = getch();
+    if (!c || c == 0xE0) {
+       c <<= 8;
+       c |= getch();
+    }
+    return c;
+}
+#endif
+
+/*****************************************************************
+    in case of fatal errors
+*****************************************************************/
+void
+error_ex()
+{
+    char *sp;
+
+    memcpy(fitbuf, "         ", 9);    /* start after GNUPLOT> */
+    sp = strchr(fitbuf, NUL);
+    while (*--sp == '\n');
+    strcpy(sp + 1, "\n\n");    /* terminate with exactly 2 newlines */
+    fputs(fitbuf, STANDARD);
+    if (log_f) {
+       fprintf(log_f, "BREAK: %s", fitbuf);
+       (void) fclose(log_f);
+       log_f = NULL;
+    }
+    if (func.at) {
+       free_at(func.at);               /* release perm. action table */
+       func.at = (struct at_type *) NULL;
+    }
+    /* restore original SIGINT function */
+#if !defined(ATARI) && !defined(MTOS)
+    interrupt_setup();
+#endif
+
+    bail_to_command_line();
+}
+
+/* HBB 990829: removed the debug print routines */
+/*****************************************************************
+    Marquardt's nonlinear least squares fit
+*****************************************************************/
+static marq_res_t
+marquardt(double a[], double **C, double *chisq, double *lambda)
+{
+    int i, j;
+    static double *da = 0,     /* delta-step of the parameter */
+    *temp_a = 0,               /* temptative new params set   */
+    *d = 0, *tmp_d = 0, **tmp_C = 0, *residues = 0;
+    double tmp_chisq;
+
+    /* Initialization when lambda == -1 */
+
+    if (*lambda == -1) {       /* Get first chi-square check */
+       TBOOLEAN analyze_ret;
+
+       temp_a = vec(num_params);
+       d = vec(num_data + num_params);
+       tmp_d = vec(num_data + num_params);
+       da = vec(num_params);
+       residues = vec(num_data + num_params);
+       tmp_C = matr(num_data + num_params, num_params);
+
+       analyze_ret = analyze(a, C, d, chisq);
+
+       /* Calculate a useful startup value for lambda, as given by Schwarz */
+       /* FIXME: this is doesn't turn out to be much better, really... */
+       if (startup_lambda != 0)
+           *lambda = startup_lambda;
+       else {
+           *lambda = 0;
+           for (i = 0; i < num_data; i++)
+               for (j = 0; j < num_params; j++)
+                   *lambda += C[i][j] * C[i][j];
+           *lambda = sqrt(*lambda / num_data / num_params);
+       }
+
+       /* Fill in the lower square part of C (the diagonal is filled in on
+          each iteration, see below) */
+       for (i = 0; i < num_params; i++)
+           for (j = 0; j < i; j++)
+               C[num_data + i][j] = 0, C[num_data + j][i] = 0;
+       return analyze_ret ? OK : ML_ERROR;
+    }
+    /* once converged, free dynamic allocated vars */
+
+    if (*lambda == -2) {
+       free(d);
+       free(tmp_d);
+       free(da);
+       free(temp_a);
+       free(residues);
+       free_matr(tmp_C);
+       return OK;
+    }
+    /* Givens calculates in-place, so make working copies of C and d */
+
+    for (j = 0; j < num_data + num_params; j++)
+       memcpy(tmp_C[j], C[j], num_params * sizeof(double));
+    memcpy(tmp_d, d, num_data * sizeof(double));
+
+    /* fill in additional parts of tmp_C, tmp_d */
+
+    for (i = 0; i < num_params; i++) {
+       /* fill in low diag. of tmp_C ... */
+       tmp_C[num_data + i][i] = *lambda;
+       /* ... and low part of tmp_d */
+       tmp_d[num_data + i] = 0;
+    }
+
+    /* FIXME: residues[] isn't used at all. Why? Should it be used? */
+
+    Givens(tmp_C, tmp_d, da, residues, num_params + num_data, num_params, 1);
+
+    /* check if trial did ameliorate sum of squares */
+
+    for (j = 0; j < num_params; j++)
+       temp_a[j] = a[j] + da[j];
+
+    if (!analyze(temp_a, tmp_C, tmp_d, &tmp_chisq)) {
+       /* FIXME: will never be reached: always returns TRUE */
+       return ML_ERROR;
+    }
+    if (tmp_chisq < *chisq) {  /* Success, accept new solution */
+       if (*lambda > MIN_LAMBDA) {
+           (void) putc('/', stderr);
+           *lambda /= lambda_down_factor;
+       }
+       *chisq = tmp_chisq;
+       for (j = 0; j < num_data; j++) {
+           memcpy(C[j], tmp_C[j], num_params * sizeof(double));
+           d[j] = tmp_d[j];
+       }
+       for (j = 0; j < num_params; j++)
+           a[j] = temp_a[j];
+       return BETTER;
+    } else {                   /* failure, increase lambda and return */
+       (void) putc('*', stderr);
+       *lambda *= lambda_up_factor;
+       return WORSE;
+    }
+}
+
+
+/*****************************************************************
+    compute chi-square and numeric derivations
+*****************************************************************/
+/* used by marquardt to evaluate the linearized fitting matrix C and
+ * vector d, fills in only the top part of C and d I don't use a
+ * temporary array zfunc[] any more. Just use d[] instead.  */
+/* FIXME: in the new code, this function doesn't really do enough to
+ * be useful. Maybe it ought to be deleted, i.e. integrated with
+ * calculate() ? */
+static TBOOLEAN
+analyze(double a[], double **C, double d[], double *chisq)
+{
+    int i, j;
+
+    *chisq = 0;
+    calculate(d, C, a);
+
+    for (i = 0; i < num_data; i++) {
+       /* note: order reversed, as used by Schwarz */
+       d[i] = (d[i] - fit_z[i]) / err_data[i];
+       *chisq += d[i] * d[i];
+       for (j = 0; j < num_params; j++)
+           C[i][j] /= err_data[i];
+    }
+    /* FIXME: why return a value that is always TRUE ? */
+    return TRUE;
+}
+
+
+/* To use the more exact, but slower two-side formula, activate the
+   following line: */
+/*#define TWO_SIDE_DIFFERENTIATION */
+/*****************************************************************
+    compute function values and partial derivatives of chi-square
+*****************************************************************/
+static void
+calculate(double *zfunc, double **dzda, double a[])
+{
+    int k, p;
+    double tmp_a;
+    double *tmp_high, *tmp_pars;
+#ifdef TWO_SIDE_DIFFERENTIATION
+    double *tmp_low;
+#endif
+
+    tmp_high = vec(num_data);  /* numeric derivations */
+#ifdef TWO_SIDE_DIFFERENTIATION
+    tmp_low = vec(num_data);
+#endif
+    tmp_pars = vec(num_params);
+
+    /* first function values */
+
+    call_gnuplot(a, zfunc);
+
+    /* then derivatives */
+
+    for (p = 0; p < num_params; p++)
+       tmp_pars[p] = a[p];
+    for (p = 0; p < num_params; p++) {
+       tmp_a = fabs(a[p]) < NEARLY_ZERO ? NEARLY_ZERO : a[p];
+       tmp_pars[p] = tmp_a * (1 + DELTA);
+       call_gnuplot(tmp_pars, tmp_high);
+#ifdef TWO_SIDE_DIFFERENTIATION
+       tmp_pars[p] = tmp_a * (1 - DELTA);
+       call_gnuplot(tmp_pars, tmp_low);
+#endif
+       for (k = 0; k < num_data; k++)
+#ifdef TWO_SIDE_DIFFERENTIATION
+           dzda[k][p] = (tmp_high[k] - tmp_low[k]) / (2 * tmp_a * DELTA);
+#else
+           dzda[k][p] = (tmp_high[k] - zfunc[k]) / (tmp_a * DELTA);
+#endif
+       tmp_pars[p] = a[p];
+    }
+
+#ifdef TWO_SIDE_DIFFERENTIATION
+    free(tmp_low);
+#endif
+    free(tmp_high);
+    free(tmp_pars);
+}
+
+
+/*****************************************************************
+    call internal gnuplot functions
+*****************************************************************/
+static void
+call_gnuplot(double *par, double *data)
+{
+    int i;
+    struct value v;
+
+    /* set parameters first */
+    for (i = 0; i < num_params; i++) {
+       (void) Gcomplex(&v, par[i], 0.0);
+       setvar(par_name[i], v);
+    }
+
+    for (i = 0; i < num_data; i++) {
+       /* calculate fit-function value */
+       (void) Gcomplex(&func.dummy_values[0], fit_x[i], 0.0);
+       (void) Gcomplex(&func.dummy_values[1], fit_y[i], 0.0);
+       evaluate_at(func.at, &v);
+       if (undefined)
+           Eex("Undefined value during function evaluation");
+       data[i] = real(&v);
+    }
+}
+
+
+/*****************************************************************
+    handle user interrupts during fit
+*****************************************************************/
+static TBOOLEAN
+fit_interrupt()
+{
+    while (TRUE) {
+       fputs("\n\n(S)top fit, (C)ontinue, (E)xecute FIT_SCRIPT:  ", STANDARD);
+       switch (getc(stdin)) {
+
+       case EOF:
+       case 's':
+       case 'S':
+           fputs("Stop.", STANDARD);
+           user_stop = TRUE;
+           return FALSE;
+
+       case 'c':
+       case 'C':
+           fputs("Continue.", STANDARD);
+           return TRUE;
+
+       case 'e':
+       case 'E':{
+               int i;
+               struct value v;
+               const 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++) {
+                   (void) Gcomplex(&v, a[i], 0.0);
+                   setvar(par_name[i], v);
+               }
+               safe_strncpy(gp_input_line, tmp, gp_input_line_len);
+               (void) do_line();
+           }
+       }
+    }
+    return TRUE;
+}
+
+
+/*****************************************************************
+    frame routine for the marquardt-fit
+*****************************************************************/
+static TBOOLEAN
+regress(double a[])
+{
+    double **covar, *dpar, **C, chisq, last_chisq, lambda;
+    int iter, i, j;
+    marq_res_t res;
+    struct udvt_entry *v;      /* For exporting results to the user */
+
+    chisq = last_chisq = INFINITY;
+    C = matr(num_data + num_params, num_params);
+    lambda = -1;               /* use sign as flag */
+    iter = 0;                  /* iteration counter  */
+
+    /* ctrlc now serves as Hotkey */
+    ctrlc_setup();
+
+    /* Initialize internal variables and 1st chi-square check */
+    if ((res = marquardt(a, C, &chisq, &lambda)) == ML_ERROR)
+       Eex("FIT: error occurred during fit");
+    res = BETTER;
+
+    show_fit(iter, chisq, chisq, a, lambda, STANDARD);
+    show_fit(iter, chisq, chisq, a, lambda, log_f);
+
+    /* Reset flag describing fit result status */
+       v = add_udv_by_name("FIT_CONVERGED");
+       v->udv_undef = FALSE;
+       Ginteger(&v->udv_value, 0);
+
+    /* MAIN FIT LOOP: do the regression iteration */
+
+    /* HBB 981118: initialize new variable 'user_break' */
+    user_stop = FALSE;
+
+    do {
+/*
+ *  MSDOS defines signal(SIGINT) but doesn't handle it through
+ *  real interrupts. So there remain cases in which a ctrl-c may
+ *  be uncaught by signal. We must use kbhit() instead that really
+ *  serves the keyboard interrupt (or write an own interrupt func
+ *  which also generates #ifdefs)
+ *
+ *  I hope that other OSes do it better, if not... add #ifdefs :-(
+ *  EMX does not have kbhit.
+ *
+ *  HBB: I think this can be enabled for DJGPP V2. SIGINT is actually
+ *  handled there, AFAIK.
+ */
+#if ((defined(MSDOS) || defined(DOS386)) && !defined(__EMX__)) || defined(ATARI) || defined(MTOS)
+       if (kbhit()) {
+           do {
+               getchx();
+           } while (kbhit());
+           ctrlc_flag = TRUE;
+       }
+#endif
+
+       if (ctrlc_flag) {
+           show_fit(iter, chisq, last_chisq, a, lambda, STANDARD);
+           ctrlc_flag = FALSE;
+           if (!fit_interrupt())       /* handle keys */
+               break;
+       }
+       if (res == BETTER) {
+           iter++;
+           last_chisq = chisq;
+       }
+       if ((res = marquardt(a, C, &chisq, &lambda)) == BETTER)
+           show_fit(iter, chisq, last_chisq, a, lambda, STANDARD);
+    } while ((res != ML_ERROR)
+            && (lambda < MAX_LAMBDA)
+            && ((maxiter == 0) || (iter <= maxiter))
+            && (res == WORSE
+                || ((chisq > NEARLY_ZERO)
+                    ? ((last_chisq - chisq) / chisq)
+                    : (last_chisq - chisq)) > epsilon
+            )
+       );
+
+    /* fit done */
+
+#if !defined(ATARI) && !defined(MTOS)
+    /* restore original SIGINT function */
+    interrupt_setup();
+#endif
+
+    /* HBB 970304: the maxiter patch: */
+    if ((maxiter > 0) && (iter > maxiter)) {
+       Dblf2("\nMaximum iteration count (%d) reached. Fit stopped.\n", maxiter);
+    } else if (user_stop) {
+       Dblf2("\nThe fit was stopped by the user after %d iterations.\n", iter);
+    } else {
+       Dblf2("\nAfter %d iterations the fit converged.\n", iter);
+       v = add_udv_by_name("FIT_CONVERGED");
+       v->udv_undef = FALSE;
+       Ginteger(&v->udv_value, 1);
+    }
+
+    Dblf2("final sum of squares of residuals : %g\n", chisq);
+    if (chisq > NEARLY_ZERO) {
+       Dblf2("rel. change during last iteration : %g\n\n", (chisq - last_chisq) / chisq);
+    } else {
+       Dblf2("abs. change during last iteration : %g\n\n", (chisq - last_chisq));
+    }
+
+    if (res == ML_ERROR)
+       Eex("FIT: error occurred during fit");
+
+    /* compute errors in the parameters */
+
+#ifdef GP_FIT_ERRVARS
+    if (fit_errorvariables)
+       /* Set error variable to zero before doing this */
+       /* Thus making sure they are created */
+       for (i = 0; i < num_params; i++)
+           setvarerr(par_name[i], 0.0);
+#endif
+
+    if (num_data == num_params) {
+       int k;
+
+       Dblf("\nExactly as many data points as there are parameters.\n");
+       Dblf("In this degenerate case, all errors are zero by definition.\n\n");
+       Dblf("Final set of parameters \n");
+       Dblf("======================= \n\n");
+       for (k = 0; k < num_params; k++)
+           Dblf3("%-15.15s = %-15g\n", par_name[k], a[k]);
+    } else if (chisq < NEARLY_ZERO) {
+       int k;
+
+       Dblf("\nHmmmm.... Sum of squared residuals is zero. Can't compute errors.\n\n");
+       Dblf("Final set of parameters \n");
+       Dblf("======================= \n\n");
+       for (k = 0; k < num_params; k++)
+           Dblf3("%-15.15s = %-15g\n", par_name[k], a[k]);
+    } else {
+       int ndf          = num_data - num_params; 
+       double stdfit    = sqrt(chisq/ndf);
+       
+       Dblf2("degrees of freedom    (FIT_NDF)                        : %d\n", ndf);
+       Dblf2("rms of residuals      (FIT_STDFIT) = sqrt(WSSR/ndf)    : %g\n", stdfit);
+       Dblf2("variance of residuals (reduced chisquare) = WSSR/ndf   : %g\n\n", chisq/ndf);
+
+       /* Export these to user-accessible variables */
+       v = add_udv_by_name("FIT_NDF");
+       v->udv_undef = FALSE;
+       Ginteger(&v->udv_value, ndf);
+       v = add_udv_by_name("FIT_STDFIT");
+       v->udv_undef = FALSE;
+       Gcomplex(&v->udv_value, stdfit, 0);
+       v = add_udv_by_name("FIT_WSSR");
+       v->udv_undef = FALSE;
+       Gcomplex(&v->udv_value, chisq, 0);
+
+       /* get covariance-, Korrelations- and Kurvature-Matrix */
+       /* and errors in the parameters                     */
+
+       /* compute covar[][] directly from C */
+       Givens(C, 0, 0, 0, num_data, num_params, 0);
+
+       /* Use lower square of C for covar */
+       covar = C + num_data;
+       Invert_RtR(C, covar, num_params);
+
+       /* calculate unscaled parameter errors in dpar[]: */
+       dpar = vec(num_params);
+       for (i = 0; i < num_params; i++) {
+           /* FIXME: can this still happen ? */
+           if (covar[i][i] <= 0.0)     /* HBB: prevent floating point exception later on */
+               Eex("Calculation error: non-positive diagonal element in covar. matrix");
+           dpar[i] = sqrt(covar[i][i]);
+       }
+
+       /* transform covariances into correlations */
+       for (i = 0; i < num_params; i++) {
+           /* only lower triangle needs to be handled */
+           for (j = 0; j <= i; j++)
+               covar[i][j] /= dpar[i] * dpar[j];
+       }
+
+       /* scale parameter errors based on chisq */
+       chisq = sqrt(chisq / (num_data - num_params));
+       for (i = 0; i < num_params; i++)
+           dpar[i] *= chisq;
+
+       Dblf("Final set of parameters            Asymptotic Standard Error\n");
+       Dblf("=======================            ==========================\n\n");
+
+       for (i = 0; i < num_params; i++) {
+           double temp = (fabs(a[i]) < NEARLY_ZERO)
+               ? 0.0
+               : fabs(100.0 * dpar[i] / a[i]);
+
+           Dblf6("%-15.15s = %-15g  %-3.3s %-12.4g (%.4g%%)\n",
+                 par_name[i], a[i], PLUSMINUS, dpar[i], temp);
+#ifdef GP_FIT_ERRVARS
+           if (fit_errorvariables)
+               setvarerr(par_name[i], dpar[i]);
+#endif
+       }
+
+       Dblf("\n\ncorrelation matrix of the fit parameters:\n\n");
+       Dblf("               ");
+
+       for (j = 0; j < num_params; j++)
+           Dblf2("%-6.6s ", par_name[j]);
+
+       Dblf("\n");
+       for (i = 0; i < num_params; i++) {
+           Dblf2("%-15.15s", par_name[i]);
+           for (j = 0; j <= i; j++) {
+               /* Only print lower triangle of symmetric matrix */
+               Dblf2("%6.3f ", covar[i][j]);
+           }
+           Dblf("\n");
+       }
+
+       free(dpar);
+    }
+
+    /* HBB 990220: re-imported this snippet from older versions. Finally,
+     * some user noticed that it *is* necessary, after all. Not even
+     * Carsten Grammes himself remembered what it was for... :-(
+     * The thing is: the value of the last parameter is not reset to
+     * its original one after the derivatives have been calculated
+     */
+    /* restore last parameter's value (not done by calculate) */
+    {
+       struct value val;
+       Gcomplex(&val, a[num_params - 1], 0.0);
+       setvar(par_name[num_params - 1], val);
+    }
+
+    /* call destructor for allocated vars */
+    lambda = -2;               /* flag value, meaning 'destruct!' */
+    (void) marquardt(a, C, &chisq, &lambda);
+
+    free_matr(C);
+    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\
+ WSSR        : %-15g   delta(WSSR)/WSSR   : %g\n\
+ delta(WSSR) : %-15g   limit for stopping : %g\n\
+ lambda          : %g\n\n%s parameter values\n\n",
+           i, chisq, chisq > NEARLY_ZERO ? (chisq - last_chisq) / chisq : 0.0,
+           chisq - last_chisq, epsilon, lambda,
+           (i > 0 ? "resultant" : "initial set of free"));
+    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 TBOOLEAN
+is_empty(char *s)
+{
+    while (*s == ' ' || *s == '\t' || *s == '\n')
+       s++;
+    return (TBOOLEAN) (*s == '#' || *s == '\0');
+}
+
+
+/*****************************************************************
+    get next word of a multi-word string, advance pointer
+*****************************************************************/
+static 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;
+}
+
+
+/*****************************************************************
+    check for variable identifiers
+*****************************************************************/
+static TBOOLEAN
+is_variable(char *s)
+{
+    while (*s != '\0') {
+       if (!isalnum((unsigned char) *s) && *s != '_')
+           return FALSE;
+       s++;
+    }
+    return TRUE;
+}
+
+/*****************************************************************
+    first time settings
+*****************************************************************/
+void
+init_fit()
+{
+    func.at = (struct at_type *) NULL; /* need to parse 1 time */
+}
+
+
+/*****************************************************************
+           Set a GNUPLOT user-defined variable
+******************************************************************/
+
+static void
+setvar(char *varname, struct value data)
+{
+    struct udvt_entry *udv_ptr = add_udv_by_name(varname);
+    udv_ptr->udv_value = data;
+    udv_ptr->udv_undef = FALSE;
+}
+
+#ifdef GP_FIT_ERRVARS
+/*****************************************************************
+            Set a GNUPLOT user-defined variable for an error
+            variable: so take the parameter name, turn it
+            into an error parameter name (e.g. a to a_err)
+            and then set it.
+******************************************************************/
+static void
+setvarerr(char *varname, double value)
+{
+       struct value errval;    /* This will hold the gnuplot value created from value*/
+       char* pErrValName;      /* The name of the (new) error variable */
+
+       /* Create the variable name by appending _err */
+       pErrValName = gp_alloc(strlen(varname)+sizeof(char)+4, "");
+
+       sprintf(pErrValName,"%s_err",varname);
+       Gcomplex(&errval, value, 0.0);
+       setvar(pErrValName, errval);
+       free(pErrValName);
+}
+#endif /* GP_FIT_ERRVARS */
+
+/*****************************************************************
+    Read INTGR Variable value, return 0 if undefined or wrong type
+*****************************************************************/
+static int
+getivar(const char *varname)
+{
+    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
+   I dont think it's a problem that it's an integer - div
+*****************************************************************/
+static double
+getdvar(const char *varname)
+{
+    struct udvt_entry *udv_ptr = first_udv;
+
+    for (; udv_ptr; udv_ptr = udv_ptr->next_udv)
+       if (strcmp(varname, udv_ptr->udv_name) == 0)
+           return real(&(udv_ptr->udv_value));
+
+    /* get here => not found */
+    return 0;
+}
+
+/*****************************************************************
+   like getdvar, but
+   - convert it from integer to real if necessary
+   - create it with value INITIAL_VALUE if not found or undefined
+*****************************************************************/
+static double
+createdvar(char *varname, double value)
+{
+    struct udvt_entry *udv_ptr = first_udv;
+
+    for (; udv_ptr; udv_ptr = udv_ptr->next_udv)
+       if (strcmp(varname, udv_ptr->udv_name) == 0) {
+           if (udv_ptr->udv_undef) {
+               udv_ptr->udv_undef = 0;
+               (void) Gcomplex(&udv_ptr->udv_value, value, 0.0);
+           } else if (udv_ptr->udv_value.type == INTGR) {
+               (void) Gcomplex(&udv_ptr->udv_value, (double) udv_ptr->udv_value.v.int_val, 0.0);
+           }
+           return real(&(udv_ptr->udv_value));
+       }
+    /* get here => not found */
+
+    {
+       struct value tempval;
+       (void) Gcomplex(&tempval, value, 0.0);
+       setvar(varname, tempval);
+    }
+
+    return value;
+}
+
+
+/*****************************************************************
+    Split Identifier into path and filename
+*****************************************************************/
+static void
+splitpath(char *s, char *p, char *f)
+{
+    char *tmp = s + strlen(s) - 1;
+
+    while (tmp >= s && *tmp != '\\' && *tmp != '/' && *tmp != ':')
+       tmp--;
+    /* FIXME HBB 20010121: unsafe! Sizes of 'f' and 'p' are not known.
+     * May write past buffer end. */
+    strcpy(f, tmp + 1);
+    memcpy(p, s, (size_t) (tmp - s + 1));
+    p[tmp - s + 1] = NUL;
+}
+
+
+/* argument: char *fn */
+#define VALID_FILENAME(fn) ((fn) != NULL && (*fn) != '\0')
+
+/*****************************************************************
+    write the actual parameters to start parameter file
+*****************************************************************/
+void
+update(char *pfile, char *npfile)
+{
+    char fnam[256], path[256], sstr[256], pname[64], tail[127], *s = sstr, *tmp, c;
+    char ifilename[256], *ofilename;
+    FILE *of, *nf;
+    double pval;
+
+
+    /* update pfile npfile:
+       if npfile is a valid file name,
+       take pfile as input file and
+       npfile as output file
+     */
+    if (VALID_FILENAME(npfile)) {
+       safe_strncpy(ifilename, pfile, sizeof(ifilename));
+       ofilename = npfile;
+    } else {
+#ifdef BACKUP_FILESYSTEM
+       /* filesystem will keep original as previous version */
+       safe_strncpy(ifilename, pfile, sizeof(ifilename));
+#else
+       backup_file(ifilename, pfile);  /* will Eex if it fails */
+#endif
+       ofilename = pfile;
+    }
+
+    /* split into path and filename */
+    splitpath(ifilename, path, fnam);
+    if (!(of = loadpath_fopen(ifilename, "r")))
+       Eex2("parameter file %s could not be read", ifilename);
+
+    if (!(nf = fopen(ofilename, "w")))
+       Eex2("new parameter file %s could not be created", ofilename);
+
+    while (fgets(s = sstr, sizeof(sstr), of) != NULL) {
+
+       if (is_empty(s)) {
+           fputs(s, nf);       /* preserve comments */
+           continue;
+       }
+       if ((tmp = strchr(s, '#')) != NULL) {
+           safe_strncpy(tail, tmp, sizeof(tail));
+           *tmp = NUL;
+       } else
+           strcpy(tail, "\n");
+
+       tmp = get_next_word(&s, &c);
+       if (!is_variable(tmp) || strlen(tmp) > MAX_ID_LEN) {
+           (void) fclose(nf);
+           (void) fclose(of);
+           Eex2("syntax error in parameter file %s", fnam);
+       }
+       safe_strncpy(pname, tmp, sizeof(pname));
+       /* next must be '=' */
+       if (c != '=') {
+           tmp = strchr(s, '=');
+           if (tmp == NULL) {
+               (void) fclose(nf);
+               (void) fclose(of);
+               Eex2("syntax error in parameter file %s", fnam);
+           }
+           s = tmp + 1;
+       }
+       tmp = get_next_word(&s, &c);
+       if (!sscanf(tmp, "%lf", &pval)) {
+           (void) fclose(nf);
+           (void) fclose(of);
+           Eex2("syntax error in parameter file %s", fnam);
+       }
+       if ((tmp = get_next_word(&s, &c)) != NULL) {
+           (void) fclose(nf);
+           (void) fclose(of);
+           Eex2("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(nf) || fclose(of))
+       Eex("I/O error during update");
+
+}
+
+
+/*****************************************************************
+    Backup a file by renaming it to something useful. Return
+    the new name in tofile
+*****************************************************************/
+
+/* tofile must point to a char array[] or allocated data. See update() */
+
+static void
+backup_file(char *tofile, const char *fromfile)
+{
+#if defined (WIN32) || defined(MSDOS) || defined(VMS)
+    char *tmpn;
+#endif
+
+    /* win32 needs to have two attempts at the rename, since it may
+     * be running on win32s with msdos 8.3 names
+     */
+
+/* first attempt, for all o/s other than MSDOS */
+
+#ifndef MSDOS
+
+    strcpy(tofile, fromfile);
+
+#ifdef VMS
+    /* replace all dots with _, since we will be adding the only
+     * dot allowed in VMS names
+     */
+    while ((tmpn = strchr(tofile, '.')) != NULL)
+       *tmpn = '_';
+#endif /*VMS */
+
+    strcat(tofile, BACKUP_SUFFIX);
+
+    if (rename(fromfile, tofile) == 0)
+       return;                 /* hurrah */
+#endif
+
+
+#if defined (WIN32) || defined(MSDOS)
+
+    /* first attempt for msdos. Second attempt for win32s */
+
+    /* Copy only the first 8 characters of the filename, to comply
+     * with the restrictions of FAT filesystems. */
+    safe_strncpy(tofile, fromfile, 8 + 1);
+
+    while ((tmpn = strchr(tofile, '.')) != NULL)
+       *tmpn = '_';
+
+    strcat(tofile, BACKUP_SUFFIX);
+
+    if (rename(fromfile, tofile) == 0)
+       return;                 /* success */
+
+#endif /* win32 || msdos */
+
+    /* get here => rename failed. */
+    Eex3("Could not rename file %s to %s", fromfile, tofile);
+}
+
+/* A modified copy of save.c:save_range(), but this one reports
+ * _current_ values, not the 'set' ones, by default */
+static void
+log_axis_restriction(FILE *log_f, AXIS_INDEX axis)
+{
+    char s[80];
+    AXIS *this_axis = axis_array + axis;
+
+    fprintf(log_f, "\t%s range restricted to [", axis_defaults[axis].name);
+    if (this_axis->autoscale & AUTOSCALE_MIN) {
+       putc('*', log_f);
+    } else if (this_axis->is_timedata) {
+       putc('"', log_f);
+       gstrftime(s, 80, this_axis->timefmt, this_axis->min);
+       fputs(s, log_f);
+       putc('"', log_f);
+    } else {
+       fprintf(log_f, "%#g", this_axis->min);
+    }
+
+    fputs(" : ", log_f);
+    if (this_axis->autoscale & AUTOSCALE_MAX) {
+       putc('*', log_f);
+    } else if (this_axis->is_timedata) {
+       putc('"', log_f);
+       gstrftime(s, 80, this_axis->timefmt, this_axis->max);
+       fputs(s, log_f);
+       putc('"', log_f);
+    } else {
+       fprintf(log_f, "%#g", this_axis->max);
+    }
+    fputs("]\n", log_f);
+}
+
+/*****************************************************************
+    Interface to the classic gnuplot-software
+*****************************************************************/
+
+void
+fit_command()
+{
+/* HBB 20000430: revised this completely, to make it more similar to
+ * what plot3drequest() does */
+
+    int dummy_x = -1, dummy_y = -1;    /* eg  fit [u=...] [v=...] */
+    int zrange_token = -1;
+    TBOOLEAN is_a_3d_fit;
+
+    int i;
+    double v[4];
+    double tmpd;
+    time_t timer;
+    int token1, token2, token3;
+    char *tmp, *file_name;
+
+    c_token++;
+
+    /* first look for a restricted x fit range... */
+
+    /* put stuff into arrays to simplify access */
+    AXIS_INIT3D(FIRST_X_AXIS, 0, 0);
+    AXIS_INIT3D(FIRST_Y_AXIS, 0, 0);
+    AXIS_INIT3D(FIRST_Z_AXIS, 0, 1);
+
+    /* use global default indices in axis.c to simplify access to
+     * per-axis variables */
+    x_axis = FIRST_X_AXIS;
+    y_axis = FIRST_Y_AXIS;
+    z_axis = FIRST_Z_AXIS;
+
+    PARSE_NAMED_RANGE(FIRST_X_AXIS, dummy_x);
+    PARSE_NAMED_RANGE(FIRST_Y_AXIS, dummy_y);
+    /* HBB 980401: new: allow restricting the z range as well */
+    if (equals(c_token, "["))
+      zrange_token = c_token;
+    PARSE_RANGE(FIRST_Z_AXIS);
+
+
+    /* now compile the function */
+
+    token1 = c_token;
+
+    if (func.at) {
+       free_at(func.at);
+       func.at = NULL;         /* in case perm_at() does int_error */
+    }
+    dummy_func = &func;
+
+
+    /* set the dummy variable names */
+
+    if (dummy_x >= 0)
+       copy_str(c_dummy_var[0], dummy_x, MAX_ID_LEN);
+    else
+       strcpy(c_dummy_var[0], set_dummy_var[0]);
+
+    if (dummy_y >= 0)
+       copy_str(c_dummy_var[0], dummy_y, MAX_ID_LEN);
+    else
+       strcpy(c_dummy_var[1], set_dummy_var[1]);
+
+    func.at = perm_at();
+    dummy_func = NULL;
+
+    token2 = c_token;
+
+    /* get filename */
+    file_name = try_to_get_string();
+    if (!file_name)
+       int_error(c_token, "missing filename");
+
+    /* use datafile module to parse the datafile and qualifiers */
+    df_set_plot_mode(MODE_QUERY);  /* Does nothing except for binary datafiles */
+    columns = df_open(file_name, 4);   /* up to 4 using specs allowed */
+    free(file_name);
+    if (columns < 0)
+       int_error(NO_CARET,"Can't read data file");
+    if (columns == 1)
+       int_error(c_token, "Need 2 to 4 using specs");
+    is_a_3d_fit = (columns == 4);
+
+    /* The following patch was made by Remko Scharroo, 25-Mar-1999
+     * We need to check if one of the columns is time data, like
+     * in plot2d and plot3d */
+
+    if (X_AXIS.is_timedata) {
+       if (columns < 2)
+           int_error(c_token, "Need full using spec for x time data");
+    }
+    if (Y_AXIS.is_timedata) {
+       if (columns < 1)
+           int_error(c_token, "Need using spec for y time data");
+    }
+    df_axis[0] = FIRST_X_AXIS;
+    df_axis[1] = (is_a_3d_fit) ? FIRST_Y_AXIS : FIRST_Z_AXIS;
+    /* don't parse delta_z as times */
+    df_axis[2] = (is_a_3d_fit) ? FIRST_Z_AXIS : -1;
+    df_axis[3] = -1;
+
+    /* HBB 20000430: No need for such a check for the z axis. That
+     * axis is only used iff columns==4, i.e. the detection method for
+     * 3d fits requires a 4 column 'using', already. */
+    /* End of patch by Remko Scharroo */
+
+
+    /* HBB 980401: if this is a single-variable fit, we shouldn't have
+     * allowed a variable name specifier for 'y': */
+    if ((dummy_y >= 0) && !is_a_3d_fit)
+       int_error(dummy_y, "Can't re-name 'y' in a one-variable fit");
+
+    /* HBB 981210: two range specs mean different things, depending
+     * on wether this is a 2D or 3D fit */
+    if (!is_a_3d_fit) {
+       if (zrange_token != -1)
+           int_error(zrange_token, "Three range-specs not allowed in on-variable fit");
+       else {
+           /* 2D fit, 2 ranges: second range is for *z*, not y: */
+           Z_AXIS.autoscale = Y_AXIS.autoscale;
+           if (!(Y_AXIS.autoscale & AUTOSCALE_MIN))
+               Z_AXIS.min = Y_AXIS.min;
+           if (!(Y_AXIS.autoscale & AUTOSCALE_MAX))
+               Z_AXIS.max = Y_AXIS.max;
+       }
+    }
+    /* defer actually reading the data until we have parsed the rest
+     * of the line */
+
+    token3 = c_token;
+
+    tmpd = getdvar(FITLIMIT);  /* get epsilon if given explicitly */
+    if (tmpd < 1.0 && tmpd > 0.0)
+       epsilon = tmpd;
+
+    /* HBB 970304: maxiter patch */
+    maxiter = getivar(FITMAXITER);
+
+    /* get startup value for lambda, if given */
+    tmpd = getdvar(FITSTARTLAMBDA);
+
+    if (tmpd > 0.0) {
+       startup_lambda = tmpd;
+       printf("Lambda Start value set: %g\n", startup_lambda);
+    }
+    /* get lambda up/down factor, if given */
+    tmpd = getdvar(FITLAMBDAFACTOR);
+
+    if (tmpd > 0.0) {
+       lambda_up_factor = lambda_down_factor = tmpd;
+       printf("Lambda scaling factors reset:  %g\n", lambda_up_factor);
+    }
+    *fit_script = NUL;
+    if ((tmp = getenv(FITSCRIPT)) != NULL)
+       safe_strncpy(fit_script, tmp, sizeof(fit_script));
+
+    {
+       char *logfile = getfitlogfile();
+
+       if (!log_f && !(log_f = fopen(logfile, "a")))
+           Eex2("could not open log-file %s", logfile);
+
+       if (logfile)
+           free(logfile);
+    }
+
+    fputs("\n\n*******************************************************************************\n", log_f);
+    (void) time(&timer);
+    fprintf(log_f, "%s\n\n", ctime(&timer));
+    {
+       char *line = NULL;
+
+       m_capture(&line, token2, token3 - 1);
+       fprintf(log_f, "FIT:    data read from %s\n", line);
+       free(line);
+    }
+
+    /* HBB 20040201: somebody insisted they need this ... */
+    if (AUTOSCALE_BOTH != (X_AXIS.autoscale & AUTOSCALE_BOTH))
+       log_axis_restriction(log_f, x_axis);
+    if (AUTOSCALE_BOTH != (Y_AXIS.autoscale & AUTOSCALE_BOTH))
+       log_axis_restriction(log_f, y_axis);
+
+    max_data = MAX_DATA;
+    fit_x = vec(max_data);     /* start with max. value */
+    fit_y = vec(max_data);
+    fit_z = vec(max_data);
+
+    /* first read in experimental data */
+
+    err_data = vec(max_data);
+    num_data = 0;
+
+    while ((i = df_readline(v, 4)) != DF_EOF) {
+       if (num_data >= max_data) {
+           /* increase max_data by factor of 1.5 */
+           max_data = (max_data * 3) / 2;
+           if (0
+               || !redim_vec(&fit_x, max_data)
+               || !redim_vec(&fit_y, max_data)
+               || !redim_vec(&fit_z, max_data)
+               || !redim_vec(&err_data, max_data)
+               ) {
+               /* Some of the reallocations went bad: */
+               df_close();
+               Eex2("Out of memory in fit: too many datapoints (%d)?", max_data);
+           } else {
+               /* Just so we know that the routine is at work: */
+               fprintf(STANDARD, "Max. number of data points scaled up to: %d\n",
+                       max_data);
+           }
+       } /* if (need to extend storage space) */
+
+       switch (i) {
+       case DF_MISSING:
+       case DF_UNDEFINED:
+       case DF_FIRST_BLANK:
+       case DF_SECOND_BLANK:
+           continue;
+       case 0:
+           Eex2("bad data on line %d of datafile", df_line_number);
+           break;
+       case 1:         /* only z provided */
+           v[2] = v[0];
+           v[0] = (double) df_datum;
+           break;
+       case 2:         /* x and z */
+           v[2] = v[1];
+           break;
+
+           /* only if the explicitly asked for 4 columns do we
+            * do a 3d fit. (We can get here if they didn't
+            * specify a using spec, and the file has 4 columns
+            */
+       case 4:         /* x, y, z, error */
+           if (is_a_3d_fit)
+               break;
+           /* else fall through */
+       case 3:         /* x, z, error */
+           v[3] = v[2];        /* error */
+           v[2] = v[1];        /* z */
+           break;
+
+       }
+
+       /* skip this point if it is out of range */
+       if (!(X_AXIS.autoscale & AUTOSCALE_MIN)
+           && (v[0] < X_AXIS.min))
+           continue;
+       if (!(X_AXIS.autoscale & AUTOSCALE_MAX)
+           && (v[0] > X_AXIS.max))
+           continue;
+       if (is_a_3d_fit) {
+           if (!(Y_AXIS.autoscale & AUTOSCALE_MIN)
+               && (v[1] < Y_AXIS.min))
+               continue;
+           if (!(Y_AXIS.autoscale & AUTOSCALE_MAX)
+               && (v[1] > Y_AXIS.max))
+               continue;
+       }
+       /* HBB 980401: check *z* range for all fits */
+       if (!(Z_AXIS.autoscale & AUTOSCALE_MIN)
+           && (v[2] < Z_AXIS.min))
+           continue;
+       if (!(Z_AXIS.autoscale & AUTOSCALE_MAX)
+           && (v[2] > Z_AXIS.max))
+           continue;
+
+       fit_x[num_data] = v[0];
+       fit_y[num_data] = v[1];
+       fit_z[num_data] = v[2];
+
+       /* we only use error if _explicitly_ asked for by a
+        * using spec
+        */
+       err_data[num_data++] = (columns > 2) ? v[3] : 1;
+    }
+    df_close();
+
+    if (num_data <= 1)
+       Eex("No data to fit ");
+
+    /* now resize fields to actual length: */
+    redim_vec(&fit_x, num_data);
+    redim_vec(&fit_y, num_data);
+    redim_vec(&fit_z, num_data);
+    redim_vec(&err_data, num_data);
+
+    fprintf(log_f, "        #datapoints = %d\n", num_data);
+
+    if (columns < 3)
+       fputs("        residuals are weighted equally (unit weight)\n\n", log_f);
+
+    {
+       char *line = NULL;
+
+       m_capture(&line, token1, token2 - 1);
+       fprintf(log_f, "function used for fitting: %s\n", line);
+       free(line);
+    }
+
+    /* read in parameters */
+
+    max_params = MAX_PARAMS;   /* HBB 971023: make this resizeable */
+
+    if (!equals(c_token++, "via"))
+       int_error(c_token, "Need via and either parameter list or file");
+
+    a = vec(max_params);
+    par_name = (fixstr *) gp_alloc((max_params + 1) * sizeof(fixstr),
+                                  "fit param");
+    num_params = 0;
+
+    if (isstringvalue(c_token)) {      /* It's a parameter *file* */
+       TBOOLEAN fixed;
+       double tmp_par;
+       char c, *s;
+       char sstr[MAX_LINE_LEN + 1];
+       FILE *f;
+
+       static char *viafile = NULL;
+       free(viafile);                  /* Free previous name, if any */
+       viafile = try_to_get_string();  /* Cannot fail since isstringvalue succeeded */
+       fprintf(log_f, "fitted parameters and initial values from file: %s\n\n", viafile);
+       if (!(f = loadpath_fopen(viafile, "r")))
+           Eex2("could not read parameter-file \"%s\"", viafile);
+
+       /* get parameters and values out of file and ignore fixed ones */
+
+       while (TRUE) {
+           if (!fgets(s = sstr, sizeof(sstr), f))      /* EOF found */
+               break;
+           if ((tmp = strstr(s, GP_FIXED)) != NULL) {  /* ignore fixed params */
+               *tmp = NUL;
+               fprintf(log_f, "FIXED:  %s\n", s);
+               fixed = TRUE;
+           } else
+               fixed = FALSE;
+           if ((tmp = strchr(s, '#')) != NULL)
+               *tmp = NUL;
+           if (is_empty(s))
+               continue;
+           tmp = get_next_word(&s, &c);
+           if (!is_variable(tmp) || strlen(tmp) > MAX_ID_LEN) {
+               (void) fclose(f);
+               Eex("syntax error in parameter file");
+           }
+           safe_strncpy(par_name[num_params], tmp, sizeof(fixstr));
+           /* next must be '=' */
+           if (c != '=') {
+               tmp = strchr(s, '=');
+               if (tmp == NULL) {
+                   (void) fclose(f);
+                   Eex("syntax error in parameter file");
+               }
+               s = tmp + 1;
+           }
+           tmp = get_next_word(&s, &c);
+           if (sscanf(tmp, "%lf", &tmp_par) != 1) {
+               (void) fclose(f);
+               Eex("syntax error in parameter file");
+           }
+           /* make fixed params visible to GNUPLOT */
+           if (fixed) {
+               struct value tempval;
+               (void) Gcomplex(&tempval, tmp_par, 0.0);
+               /* use parname as temp */
+               setvar(par_name[num_params], tempval);
+           } else {
+               if (num_params >= max_params) {
+                   max_params = (max_params * 3) / 2;
+                   if (0
+                       || !redim_vec(&a, max_params)
+                       || !(par_name = gp_realloc(par_name, (max_params + 1) * sizeof(fixstr), "fit param resize"))
+                       ) {
+                       (void) fclose(f);
+                       Eex("Out of memory in fit: too many parameters?");
+                   }
+               }
+               a[num_params++] = tmp_par;
+           }
+
+           if ((tmp = get_next_word(&s, &c)) != NULL) {
+               (void) fclose(f);
+               Eex("syntax error in parameter file");
+           }
+       }
+       (void) fclose(f);
+
+    } else {
+       /* not a string after via: it's a variable listing */
+
+       fputs("fitted parameters initialized with current variable values\n\n", log_f);
+       do {
+           if (!isletter(c_token))
+               Eex("no parameter specified");
+           capture(par_name[num_params], c_token, c_token, (int) sizeof(par_name[0]));
+           if (num_params >= max_params) {
+               max_params = (max_params * 3) / 2;
+               if (0
+                   || !redim_vec(&a, max_params)
+                   || !(par_name = gp_realloc(par_name, (max_params + 1) * sizeof(fixstr), "fit param resize"))
+                   ) {
+                   Eex("Out of memory in fit: too many parameters?");
+               }
+           }
+           /* create variable if it doesn't exist */
+           a[num_params] = createdvar(par_name[num_params], INITIAL_VALUE);
+           ++num_params;
+       } while (equals(++c_token, ",") && ++c_token);
+    }
+
+    redim_vec(&a, num_params);
+    par_name = (fixstr *) gp_realloc(par_name, (num_params + 1) * sizeof(fixstr), "fit param");
+
+    if (num_data < num_params)
+       Eex("Number of data points smaller than number of parameters");
+
+    /* avoid parameters being equal to zero */
+    for (i = 0; i < num_params; i++)
+       if (a[i] == 0)
+           a[i] = NEARLY_ZERO;
+
+
+    if (num_params == 0)
+       int_warn(NO_CARET, "No fittable parameters!\n");
+    else
+       (void) regress(a);
+
+    (void) fclose(log_f);
+    log_f = NULL;
+    free(fit_x);
+    free(fit_y);
+    free(fit_z);
+    free(err_data);
+    free(a);
+    free(par_name);
+    if (func.at) {
+       free_at(func.at);               /* release perm. action table */
+       func.at = (struct at_type *) NULL;
+    }
+    safe_strncpy(last_fit_command, gp_input_line, sizeof(last_fit_command));
+}
+
+#if defined(ATARI) || defined(MTOS)
+static int
+kbhit()
+{
+    fd_set rfds;
+    struct timeval timeout;
+
+    timeout.tv_sec = 0;
+    timeout.tv_usec = 0;
+    rfds = (fd_set) (1L << fileno(stdin));
+    return ((select(0, SELECT_TYPE_ARG234 &rfds, NULL, NULL, SELECT_TYPE_ARG5 &timeout) > 0) ? 1 : 0);
+}
+#endif
+
+/*
+ * Print msg to stderr and log file
+ */
+#if defined(VA_START) && defined(STDC_HEADERS)
+static void
+Dblfn(const char *fmt, ...)
+#else
+static void
+Dblfn(const char *fmt, va_dcl)
+#endif
+{
+#ifdef VA_START
+    va_list args;
+
+    VA_START(args, fmt);
+# if defined(HAVE_VFPRINTF) || _LIBC
+    vfprintf(STANDARD, fmt, args);
+    va_end(args);
+    VA_START(args, fmt);
+    vfprintf(log_f, fmt, args);
+# else
+    _doprnt(fmt, args, STANDARD);
+    _doprnt(fmt, args, log_f);
+# endif
+    va_end(args);
+#else
+    fprintf(STANDARD, fmt, a1, a2, a3, a4, a5, a6, a7, a8);
+    fprintf(log_f, fmt, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif /* VA_START */
+}
+
+/* HBB/H.Harders NEW 20020927: make fit log filename exchangeable at
+ * run-time, not only by setting an environment variable. */
+char *
+getfitlogfile()
+{
+    char *logfile = NULL;
+
+    if (fitlogfile == NULL) {
+       char *tmp = getenv(GNUFITLOG);  /* open logfile */
+
+       if (tmp != NULL && *tmp != '\0') {
+           char *tmp2 = tmp + (strlen(tmp) - 1);
+
+           /* if given log file name ends in path separator, treat it
+            * as a directory to store the default "fit.log" in */
+           if (*tmp2 == '/' || *tmp2 == '\\') {
+               logfile = gp_alloc(strlen(tmp)
+                                  + strlen(fitlogfile_default) + 1,
+                                  "logfile");
+               strcpy(logfile, tmp);
+               strcat(logfile, fitlogfile_default);
+           } else {
+               logfile = gp_strdup(tmp);
+           }
+       } else {
+           logfile = gp_strdup(fitlogfile_default);
+       }
+    } else {
+       logfile = gp_strdup(fitlogfile);
+    }
+    return logfile;
+}