-
Notifications
You must be signed in to change notification settings - Fork 99
Expand file tree
/
Copy pathfit.c
More file actions
2449 lines (2147 loc) · 75.1 KB
/
fit.c
File metadata and controls
2449 lines (2147 loc) · 75.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* 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
*
* Michele Marziani ([email protected]), 930726: Recoding of the
* Unix-like raw console I/O routines.
*
* drd: start unitialised variables at 1 rather than NEARLY_ZERO
* (fit is more likely to converge if started from 1 than 1e-30 ?)
*
* HBB ([email protected]) : 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.
*
* HBB, 971023: lifted fixed limit on number of datapoints, and number
* of parameters.
*
* HBB/H.Harders, 20020927: log file name now changeable from inside
* gnuplot, not only by setting an environment variable.
*
* Jim Van Zandt, 090201: allow fitting functions with up to five
* independent variables.
*
* Carl Michal, 120311: optionally prescale all the parameters that
* the L-M routine sees by their initial values, so that parameters
* that differ by many orders of magnitude do not cause problems.
* With decent initial guesses, fits often take fewer iterations. If
* any variables were 0, then don't do it for those variables, since
* it may do more harm than good.
*
* Thomas Mattison, 130421: brief one-line reports, based on patchset #230.
* Bastian Maerkisch, 130421: different output verbosity levels
*
* Bastian Maerkisch, 130427: remember parameters etc. of last fit and use
* this data in a subsequent update command if the parameter file does not
* exist yet.
*
* Thomas Mattison, 130508: New convergence criterion which is absolute
* reduction in chisquare for an iteration of less than epsilon*chisquare
* plus epsilon_abs (new setting). The default convergence criterion is
* always relative no matter what the chisquare is, but users now have the
* flexibility of adding an absolute convergence criterion through
* `set fit limit_abs`. Patchset #230.
*
* Ethan A Merritt, June 2013: Remove limit of 5 independent parameters.
* The limit is now the smaller of MAXDATACOLS-2 and MAX_NUM_VAR.
* Dissociate parameters other than x/y from "normal" plot axes.
* To refine more than 2 parameters, name them with `set dummy`.
* x and y still refer to axis_array[] in order to allow time/date formats.
*
* Bastian Maerkisch, Feb 2014: New syntax to specify errors. The new
* parameter 'errors' accepts a comma separated list of (dummy) variables
* to specify which (in-)dependent variable has associated errors. 'z'
* always denotes the indep. variable. 'unitweights' tells fit to use equal
* (1) weights for the fit. The new syntax removes the ambiguity between
* x:y:z:(1) and x:z:s. The old syntax is still accepted but deprecated.
*
* Alexander Taeschner, Feb 2014: Optionally take errors of independent
* variables into account.
*
* Bastian Maerkisch, Feb 2014: Split regress() into several functions
* in order to facilitate the inclusion of alternative fitting codes.
*
* Karl Ratzsch, May 2014: Add a result variable reporting the number of
* iterations
*
* Ethan Merritt, Mar 2021: Wrap the entire fit command in an exception
* handler so that a user script can recover from fit errors.
*/
#include "fit.h"
#include "alloc.h"
#include "command.h"
#include "datablock.h"
#include "datafile.h"
#include "eval.h"
#include "gplocale.h"
#include "gp_time.h"
#include "matrix.h"
#include "misc.h"
#include "plot.h"
#include "setshow.h"
#include "scanner.h" /* For legal_identifier() */
#include "specfun.h"
#include "util.h"
#include <signal.h>
/* Just temporary */
#if defined(VA_START) && defined(STDC_HEADERS)
static void Dblfn(const char *fmt, ...);
#else
static void Dblfn();
#endif
#define Dblf Dblfn
#define Dblf2 Dblfn
#define Dblf3 Dblfn
#define Dblf5 Dblfn
#define Dblf6 Dblfn
#if defined(MSDOS) /* non-blocking IO stuff */
# include <io.h>
# include <conio.h>
# include <dos.h>
#endif
#ifdef _WIN32
# include <fcntl.h>
# include "win/winmain.h"
#endif
/* constants */
#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)
# define PLUSMINUS "\xF1" /* plusminus sign */
#else
# define PLUSMINUS "+/-"
#endif
#define LASTFITCMDLENGTH 511
/* compatible with gnuplot philosophy */
#define STANDARD stderr
/* Suffix of a backup file */
#define BACKUP_SUFFIX ".old"
#define SQR(x) ((x) * (x))
/* type definitions */
enum marq_res {
OK, ML_ERROR, BETTER, WORSE
};
typedef enum marq_res marq_res_t;
/* externally visible variables: */
/* pointer to longjmp recovery point of "fit" command */
JMP_BUF *fit_env = NULL;
/* fit control */
char *fitlogfile = NULL;
TBOOLEAN fit_suppress_log = FALSE;
TBOOLEAN fit_errorvariables = TRUE;
TBOOLEAN fit_covarvariables = FALSE;
verbosity_level fit_verbosity = BRIEF;
TBOOLEAN fit_errorscaling = TRUE;
TBOOLEAN fit_prescale = TRUE;
char *fit_script = NULL;
int fit_wrap = 0;
TBOOLEAN fit_v4compatible = FALSE;
char *last_fit_command = NULL;
/* names of user control variables */
const char * FITLIMIT = "FIT_LIMIT";
const char * FITSTARTLAMBDA = "FIT_START_LAMBDA";
const char * FITLAMBDAFACTOR = "FIT_LAMBDA_FACTOR";
const char * FITMAXITER = "FIT_MAXITER";
/* private variables: */
static double epsilon = DEF_FIT_LIMIT; /* relative convergence limit */
double epsilon_abs = 0.0; /* default to zero non-relative limit */
int maxiter = 0;
static double startup_lambda = 0;
static double lambda_down_factor = LAMBDA_DOWN_FACTOR;
static double lambda_up_factor = LAMBDA_UP_FACTOR;
static const char fitlogfile_default[] = "fit.log";
static const char GNUFITLOG[] = "FIT_LOG";
static FILE *log_f = NULL;
static FILE *via_f = NULL;
static TBOOLEAN fit_show_lambda = TRUE;
static const char *GP_FIXED = "# FIXED";
static const char *FITSCRIPT = "FIT_SCRIPT";
static const char *DEFAULT_CMD = "replot"; /* if no fitscript spec. */
static int num_data;
static int num_params;
static int num_indep; /* # independent variables in fit function */
static int num_errors; /* # error columns */
static TBOOLEAN err_cols[MAX_NUM_VAR+1]; /* TRUE if variable has an associated error */
static int columns; /* # values read from data file for each point */
static double *fit_x = 0; /* all independent variable values,
e.g. value of the ith variable from
the jth data point is in
fit_x[j*num_indep+i] */
static double *fit_z = 0; /* dependent data values */
static double *err_data = 0; /* standard deviations of indep. and dependent data */
static double *a = 0; /* array of fitting parameters */
static double **regress_C = 0; /* global copy of C matrix in regress */
static void (* regress_cleanup)(void) = NULL; /* memory cleanup function callback */
static TBOOLEAN user_stop = FALSE;
static double *scale_params = 0; /* scaling values for parameters */
static struct udft_entry func;
static fixstr *par_name;
static t_value **par_udv; /* array of pointers to the "via" variables */
static fixstr *last_par_name = NULL;
static int last_num_params = 0;
static char *last_dummy_var[MAX_NUM_VAR];
/* Mar 2014 - the single hottest call path in fit was looking up the
* dummy parameters by name (4 billion times in fit.dem).
* A total waste, since they don't change. Look up once and store here.
*/
static udvt_entry *fit_dummy_udvs[MAX_NUM_VAR];
/*****************************************************************
internal Prototypes
*****************************************************************/
#if !defined(_WIN32) || defined(WGP_CONSOLE)
static RETSIGTYPE ctrlc_handle(int an_int);
#endif
static void ctrlc_setup(void);
static void fit_main(void);
static marq_res_t marquardt(double a[], double **alpha, double *chisq, double *lambda);
static void analyze(double a[], double **alpha, double beta[],
double *chisq, double **deriv);
static void calculate(double *zfunc, double **dzda, double a[]);
static void calc_derivatives(const double *par, double *data, double **deriv);
static TBOOLEAN fit_interrupt(void);
static TBOOLEAN regress(double a[]);
static void regress_init(void);
static void regress_finalize(int iter, double chisq, double last_chisq, double lambda, double **covar);
static void fit_show(int i, double chisq, double last_chisq, double *a,
double lambda, FILE * device);
static void fit_show_brief(int iter, double chisq, double last_chisq, double *parms,
double lambda, FILE * device);
static void show_results(double chisq, double last_chisq, double* a, double* dpar, double** corel);
static void log_axis_restriction(FILE *log_f, int param,
double min, double max, int autoscale, char *name);
static void print_function_definitions(struct at_type *at, FILE * device);
static TBOOLEAN is_empty(char *s);
static intgr_t getivar(const char *varname);
static double getdvar(const char *varname);
static double createdvar(char *varname, double value);
static void setvar(char *varname, double value);
static void setvarerr(char *varname, double value);
static void setvarcovar(char *varname1, char *varname2, double value);
static char *get_next_word(char **s, char *subst);
/*****************************************************************
Interface to the gnuplot "fit" command
*****************************************************************/
void
fit_command()
{
static JMP_BUF fit_jumppoint;
if (evaluate_inside_functionblock && inside_plot_command)
int_error(NO_CARET, "fit command not possible in this context");
inside_plot_command = TRUE;
/* Set up an exception handler for errors that occur during "fit".
* Normally these would return to the top level command parser via
* a longjmp from int_error() and bail_to_command_line().
* We introduce a separate jump point here so that a call to "fit"
* always returns to the call point regardless of success or error.
* The caller must then check for success.
*/
fit_env = &fit_jumppoint;
if (SETJMP(*fit_env,1)) {
fit_env = NULL;
fprintf(stderr, "*** FIT ERROR ***\n");
free(last_fit_command);
last_fit_command = NULL;
while (!END_OF_COMMAND)
c_token++;
Ginteger( &(add_udv_by_name("FIT_ERROR")->udv_value), 1);
inside_plot_command = FALSE;
return;
}
fit_main();
fit_env = NULL;
Ginteger( &(add_udv_by_name("FIT_ERROR")->udv_value), 0);
inside_plot_command = FALSE;
}
/*****************************************************************
This is called when a SIGINT occurs during fit
*****************************************************************/
#if !defined(_WIN32) || defined(WGP_CONSOLE)
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;
}
#endif
/*****************************************************************
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(_WIN32) || defined(WGP_CONSOLE))
(void) signal(SIGINT, (sigfunc) ctrlc_handle);
#endif
}
/*****************************************************************
getch that handles also function keys etc.
*****************************************************************/
#if defined(MSDOS)
int getchx(void);
int
getchx()
{
int c = getch();
if (!c || c == 0xE0) {
c <<= 8;
c |= getch();
}
return c;
}
#endif
/*****************************************************************
Clean up data structures specfic to fatal errors during fit.
After this we invoke the generic int_error(),
which in turn returrns via longjmp to the start of this "fit".
*****************************************************************/
void
error_ex(int t_num, const char *str, ...)
{
char buf[128];
va_list args;
va_start(args, str);
vsnprintf(buf, sizeof(buf), str, args);
va_end(args);
/* cleanup - free memory */
if (log_f) {
fprintf(log_f, "BREAK: %s", buf);
fclose(log_f);
log_f = NULL;
}
if (via_f) {
fclose(via_f);
via_f = NULL;
}
free(fit_x);
free(fit_z);
free(err_data);
free(a);
a = fit_x = fit_z = err_data = NULL;
if (func.at) {
free_at(func.at); /* release perm. action table */
func.at = (struct at_type *) NULL;
}
if (regress_cleanup != NULL)
(*regress_cleanup)();
/* the datafile may still be open */
df_close();
/* restore original SIGINT function */
interrupt_setup();
/* Usually int_error() returns to the top level command parser
* via longjmp at a point equivalent to program entry.
* However in this case we introduced a nested handler at the
* start of command processing in fit_command() so that control
* returns after the "fit" command regardless of success or
* failure. It is then up to the caller to continue or not
* after a failed "fit".
*/
int_error(t_num, buf);
}
/*****************************************************************
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, **deriv = 0;
double tmp_chisq;
/* Initialization when lambda == -1 */
if (*lambda == -1) { /* Get first chi-square check */
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);
deriv = NULL;
if (num_errors > 1)
deriv = matr(num_errors - 1, num_data);
analyze(a, C, d, chisq, deriv);
/* Calculate a useful startup value for lambda, as given by Schwarz */
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 OK;
}
/* once converged, free allocated vars */
if (*lambda == -2) {
free(d);
free(tmp_d);
free(da);
free(temp_a);
free(residues);
free_matr(tmp_C);
free_matr(deriv);
/* may be called more than once */
d = tmp_d = da = temp_a = residues = (double *) NULL;
tmp_C = deriv = (double **) NULL;
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;
}
Givens(tmp_C, tmp_d, da, num_params + num_data, num_params);
/* check if trial did ameliorate sum of squares */
for (j = 0; j < num_params; j++)
temp_a[j] = a[j] + da[j];
analyze(temp_a, tmp_C, tmp_d, &tmp_chisq, deriv);
/* tsm patchset 230: Changed < to <= in next line */
/* so finding exact minimum stops iteration instead of just increasing lambda. */
/* Disadvantage is that if lambda is large enough so that chisq doesn't change */
/* is taken as success. */
if (tmp_chisq <= *chisq) { /* Success, accept new solution */
if (*lambda > MIN_LAMBDA) {
if (fit_verbosity == VERBOSE)
putc('/', stderr);
*lambda /= lambda_down_factor;
}
/* update chisq, C, d, a */
*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 */
*lambda *= lambda_up_factor;
if (fit_verbosity == VERBOSE)
(void) putc('*', stderr);
else if (fit_verbosity == BRIEF) /* one-line report even if chisq increases */
fit_show_brief(-1, tmp_chisq, *chisq, temp_a, *lambda, STANDARD);
return WORSE;
}
}
/*****************************************************************
compute the (effective) error
*****************************************************************/
static double
effective_error(double **deriv, int i)
{
double tot_err;
int j, k;
if (num_errors <= 1) /* z-errors or equal weights */
tot_err = err_data[i];
else {
/* "Effective variance" according to
* Jay Orear, Am. J. Phys., Vol. 50, No. 10, October 1982
*/
tot_err = SQR(err_data[i * num_errors + (num_errors - 1)]);
for (j = 0, k = 0; j < num_indep; j++) {
if (err_cols[j]) {
tot_err += SQR(deriv[k][i] * err_data[i * num_errors + k]);
k++;
}
}
tot_err = sqrt(tot_err);
}
return tot_err;
}
/*****************************************************************
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. */
static void
analyze(double a[], double **C, double d[], double *chisq, double ** deriv)
{
int i, j;
calculate(d, C, a);
/* derivatives in indep. variables are required for
effective variance method */
if (num_errors > 1)
calc_derivatives(a, d, deriv);
for (i = 0; i < num_data; i++) {
double err = effective_error(deriv, i);
/* note: order reversed, as used by Schwarz */
d[i] = (d[i] - fit_z[i]) / err;
for (j = 0; j < num_params; j++)
C[i][j] /= err;
}
*chisq = sumsq_vec(num_data, d);
}
/*****************************************************************
compute function values and partial derivatives of chi-square
*****************************************************************/
/* To use the more exact, but slower two-side formula, activate the
following line: */
/*#define TWO_SIDE_DIFFERENTIATION */
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 in parameters */
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
*****************************************************************/
void
call_gnuplot(const double *par, double *data)
{
int i, j;
struct value v;
/* set parameters first */
for (i = 0; i < num_params; i++)
Gcomplex(par_udv[i], par[i] * scale_params[i], 0.0);
for (i = 0; i < num_data; i++) {
/* calculate fit-function value */
/* initialize extra dummy variables from the corresponding
actual variables, if any. */
for (j = 0; j < MAX_NUM_VAR; j++) {
double dummy_value;
struct udvt_entry *udv = fit_dummy_udvs[j];
if (!udv)
int_error(NO_CARET, "Internal error: lost a dummy parameter!");
if (udv->udv_value.type == CMPLX || udv->udv_value.type == INTGR)
dummy_value = real(&(udv->udv_value));
else
dummy_value = 0.0;
Gcomplex(&func.dummy_values[j], dummy_value, 0.0);
}
/* set actual dummy variables from file data */
for (j = 0; j < num_indep; j++)
Gcomplex(&func.dummy_values[j],
fit_x[i * num_indep + j], 0.0);
evaluate_at(func.at, &v);
if (undefined || isnan(real(&v))) {
/* Print useful info on undefined-function error. */
Dblf("\nCurrent data point\n");
Dblf("=========================\n");
Dblf3("%-15s = %i out of %i\n", "#", i + 1, num_data);
for (j = 0; j < num_indep; j++)
Dblf3("%-15.15s = %-15g\n", c_dummy_var[j], par[j] * scale_params[j]);
Dblf3("%-15.15s = %-15g\n", "z", fit_z[i]);
Dblf("\nCurrent set of parameters\n");
Dblf("=========================\n");
for (j = 0; j < num_params; j++)
Dblf3("%-15.15s = %-15g\n", par_name[j], par[j] * scale_params[j]);
Dblf("\n");
if (undefined) {
Eex("Undefined value during function evaluation");
} else {
Eex("Function evaluation yields NaN (\"not a number\")");
}
}
data[i] = real(&v);
}
}
/*****************************************************************
calculate derivatives wrt the parameters
*****************************************************************/
/* Used to calculate the effective variance in effective_error() */
static void
calc_derivatives(const double *par, double *data, double **deriv)
{
int i, j, k, m;
struct value v;
double h;
/* set parameters first */
for (i = 0; i < num_params; i++)
Gcomplex(par_udv[i], par[i] * scale_params[i], 0.0);
for (i = 0; i < num_data; i++) { /* loop over data points */
for (j = 0, m = 0; j < num_indep; j++) { /* loop over indep. variables */
double tmp_high;
double tmp_x;
#ifdef TWO_SIDE_DIFFERENTIATION
double tmp_low;
#endif
/* only calculate derivatives if necessary */
if (!err_cols[j])
continue;
/* set actual dummy variables from file data */
for (k = 0; k < num_indep; k++) {
if (j != k)
Gcomplex(&func.dummy_values[k],
fit_x[i * num_indep + k], 0.0);
}
tmp_x = fit_x[i * num_indep + j];
/* optimal step size */
h = GPMAX(DELTA * fabs(tmp_x), 8*1e-8*(fabs(tmp_x) + 1e-8));
Gcomplex(&func.dummy_values[j], tmp_x + h, 0.0);
evaluate_at(func.at, &v);
tmp_high = real(&v);
#ifdef TWO_SIDE_DIFFERENTIATION
Gcomplex(&func.dummy_values[j], tmp_x - h, 0.0);
evaluate_at(func.at, &v);
tmp_low = real(&v);
deriv[m][i] = (tmp_high - tmp_low) / (2 * h);
#else
deriv[m][i] = (tmp_high - data[i]) / h;
#endif
m++;
}
}
}
/*****************************************************************
handle user interrupts during fit
*****************************************************************/
static TBOOLEAN
fit_interrupt()
{
while (TRUE) {
fputs("\n\n(S)top fit, (C)ontinue, (E)xecute FIT_SCRIPT: ", STANDARD);
#ifdef _WIN32
WinRaiseConsole();
#endif
switch (getchar()) {
case EOF:
case 's':
case 'S':
fputs("Stop.\n", STANDARD);
user_stop = TRUE;
return FALSE;
case 'c':
case 'C':
fputs("Continue.\n", STANDARD);
return TRUE;
case 'e':
case 'E':{
int i;
const char *tmp = getfitscript();
fprintf(STANDARD, "executing: %s\n", tmp);
/* FIXME: Shouldn't we also set FIT_STDFIT etc? */
/* set parameters visible to gnuplot */
for (i = 0; i < num_params; i++)
Gcomplex(par_udv[i], a[i] * scale_params[i], 0.0);
do_string(tmp);
}
}
}
return TRUE;
}
/*****************************************************************
determine current setting of FIT_SCRIPT
*****************************************************************/
const char *
getfitscript(void)
{
char *tmp;
if (fit_script != NULL)
return fit_script;
if ((tmp = getenv(FITSCRIPT)) != NULL)
return tmp;
else
return DEFAULT_CMD;
}
/*****************************************************************
initial setup for regress()
*****************************************************************/
static void
regress_init(void)
{
struct udvt_entry *v; /* For exporting results to the user */
/* Reset flag describing fit result status */
v = add_udv_by_name("FIT_CONVERGED");
Ginteger(&v->udv_value, 0);
/* Ctrl-C now serves as Hotkey */
ctrlc_setup();
/* HBB 981118: initialize new variable 'user_break' */
user_stop = FALSE;
}
/*****************************************************************
finalize regression: print results and set user variables
*****************************************************************/
static void
regress_finalize(int iter, double chisq, double last_chisq, double lambda, double **covar)
{
int i, j;
struct udvt_entry *v; /* For exporting results to the user */
int ndf;
int niter;
double stdfit;
double pvalue;
double *dpar;
double **corel = NULL;
TBOOLEAN covar_invalid = FALSE;
/* restore original SIGINT function */
interrupt_setup();
/* tsm patchset 230: final progress report labels to console */
if (fit_verbosity == BRIEF)
fit_show_brief(-2, chisq, chisq, a, lambda, STANDARD);
/* tsm patchset 230: final progress report to log file */
if (!fit_suppress_log) {
if (fit_verbosity == VERBOSE)
fit_show(iter, chisq, last_chisq, a, lambda, log_f);
else
fit_show_brief(iter, chisq, last_chisq, a, lambda, log_f);
}
/* test covariance matrix */
if (covar != NULL) {
for (i = 0; i < num_params; i++) {
/* diagonal elements must be larger than zero */
if (covar[i][i] <= 0.0) {
/* Not a fatal error, but prevent floating point exception later on */
Dblf2("Calculation error: non-positive diagonal element in covar. matrix of parameter '%s'.\n", par_name[i]);
covar_invalid = TRUE;
}
}
}
/* 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 if (lambda >= MAX_LAMBDA) {
Dblf2("\nThe maximum lambda = %e was exceeded. Fit stopped.\n", MAX_LAMBDA);
} else if (covar_invalid) {
Dblf2("\nThe covariance matrix is invalid. Fit did not converge properly.\n");
} else {
Dblf2("\nAfter %d iterations the fit converged.\n", iter);
v = add_udv_by_name("FIT_CONVERGED");
Ginteger(&v->udv_value, 1);
}
/* fit results */
ndf = num_data - num_params;
stdfit = sqrt(chisq / ndf);
pvalue = 1. - chisq_cdf(ndf, chisq);
niter = iter;
/* Export these to user-accessible variables */
v = add_udv_by_name("FIT_NDF");
Ginteger(&v->udv_value, ndf);
v = add_udv_by_name("FIT_STDFIT");
Gcomplex(&v->udv_value, stdfit, 0);
v = add_udv_by_name("FIT_WSSR");
Gcomplex(&v->udv_value, chisq, 0);
v = add_udv_by_name("FIT_P");
Gcomplex(&v->udv_value, pvalue, 0);
v = add_udv_by_name("FIT_NITER");
Ginteger(&v->udv_value, niter);
/* Save final parameters. Depending on the backend and
its internal state, the last call_gnuplot may not have been
at the minimum */
for (i = 0; i < num_params; i++)
Gcomplex(par_udv[i], a[i] * scale_params[i], 0.0);
/* Set error and covariance variables to zero,
thus making sure they are created. */
if (fit_errorvariables) {
for (i = 0; i < num_params; i++)
setvarerr(par_name[i], 0.0);
}
if (fit_covarvariables) {
/* first, remove all previous covariance variables */
del_udv_by_name("FIT_COV_", TRUE);
for (i = 0; i < num_params; i++) {
for (j = 0; j < i; j++) {
setvarcovar(par_name[i], par_name[j], 0.0);
setvarcovar(par_name[j], par_name[i], 0.0);
}
setvarcovar(par_name[i], par_name[i], 0.0);
}
}
/* calculate unscaled parameter errors in dpar[]: */
dpar = vec(num_params);
if ((covar != NULL) && !covar_invalid) {
/* calculate unscaled parameter errors in dpar[]: */
for (i = 0; i < num_params; i++) {
dpar[i] = sqrt(covar[i][i]);
}
/* transform covariances into correlations */
corel = matr(num_params, num_params);
for (i = 0; i < num_params; i++) {
/* only lower triangle needs to be handled */
for (j = 0; j < i; j++)
corel[i][j] = covar[i][j] / (dpar[i] * dpar[j]);
corel[i][i] = 1.;
}
} else {
/* set all errors to zero if covariance matrix invalid or unavailable */
for (i = 0; i < num_params; i++)
dpar[i] = 0.0;
}
if (fit_errorscaling || (num_errors == 0)) {
/* scale parameter errors based on chisq */
double temp = sqrt(chisq / (num_data - num_params));
for (i = 0; i < num_params; i++)
dpar[i] *= temp;
}
/* Save user error variables. */
if (fit_errorvariables) {
for (i = 0; i < num_params; i++)
setvarerr(par_name[i], dpar[i] * scale_params[i]);
}
/* fill covariance variables if needed */
if (fit_covarvariables && (covar != NULL) && !covar_invalid) {
double scale =
(fit_errorscaling || (num_errors == 0)) ?
(chisq / (num_data - num_params)) : 1.0;
for (i = 0; i < num_params; i++) {
/* only lower triangle needs to be handled */
for (j = 0; j <= i; j++) {
double temp = scale * scale_params[i] * scale_params[j];
setvarcovar(par_name[i], par_name[j], covar[i][j] * temp);
setvarcovar(par_name[j], par_name[i], covar[i][j] * temp);
}
}