1 /* spline.c -- Do spline interpolation. */
3 /******************************************************************************
8 This program has been placed in the public domain by its author.
11 1.1 17 Dec 1985 Modify getdata() to realloc() more memory if needed.
14 spline [options] [file]
16 Spline reads pairs of numbers from file (or the standard input, if file is missing).
17 The pairs are interpreted as abscissas and ordinates of a function. The output of
18 spline consists of similar pairs generated from the input by interpolation with
19 cubic splines. (See R. W. Hamming, Numerical Methods for Scientists and Engineers,
20 2nd ed., pp. 349ff.) There are sufficiently many points in the output to appear
21 smooth when plotted (e.g., by graph(1)). The output points are approximately evenly
22 spaced and include the input points.
24 There may be one or more options of the form: -o [argument [arg2] ].
26 The available options are:
28 -a Generate abscissa values automatically. The input consists of a list of
29 ordinates. The generated abscissas are evenly spaced with spacing given by
30 the argument, or 1 if the next argument is not a number.
32 -f Set the first derivative of the spline function at the left and right end
33 points to the first and second arguments following -f. If only one numerical
34 argument follows -f then that value is used for both left and right end points.
36 -k The argument of the k option is the value of the constant used to calculate
37 the boundary values by y''[0] = ky''[1] and y''[n] = ky''[n-1]. The default
40 -m The argument gives the maximum number of input data points. The default is 1000.
41 If the input contains more than this many points additional memory will be
42 allocated. This option may be used to slightly increase efficiency for large
43 data sets or reduce the amount of memory required for small data sets.
45 -n The number of output points is given by the argument. The default value is 100.
47 -p The splines used for interpolation are forced to be periodic, i.e. y'[0] = y'[n].
48 The first and last ordinates should be equal.
50 -s Set the second derivative of the spline function at the left and right end
51 points to the first and second arguments following -s. If only one numerical
52 argument follows -s then that value is used for both left and right end points.
54 -x The argument (and arg2, if present) are the lower (and upper) limits on the
55 abscissa values in the output. If the x option is not specified these limits
56 are calculated from the data. Automatic abscissas start at the lower limit
59 The data need not be monotonic in x but all the x values must be distinct.
60 Non-numeric data in the input is ignored.
61 ******************************************************************************/
66 /* The constant DOUBLE is a compile time switch.
67 If #define DOUBLE appears here double pecision variables are
68 used to store all data and parameters.
69 Otherwise, float variables are used for the data.
70 For most smoothing and and interpolation applications single
71 precision is more than adequate.
72 Double precision is used to solve the system of linear equations
78 # define double real; /* Type used for data storage. */
79 # define IFMT "%F" /* Input format. */
80 # define OFMT "%18.14g %18.14g\n" /* Output format. */
82 # define float real; /* Type used for data storage. */
83 # define IFMT "%f" /* Input format. */
84 # define OFMT "%8.5g %8.5g\n" /* Output format. */
87 /* Numerical constants: These may be machine and/or precision dependent. */
88 #define HUGE (1.e38) /* Largest floating point number. */
89 #define EPS 1.e-5 /* Test for zero when solving linear equations. */
91 /* Default parameters */
94 #define DEFSTEP 1. /* Default step size for automatic abcissas. */
95 #define DEFBVK 0. /* Default boundary value constant. */
97 /* Boundary condition types. */
98 #define EXTRAP 0 /* Extrapolate second derivative:
99 y''(0) = k * y''(1), y''(n) = k * y''(n-1) */
100 #define FDERIV 1 /* Fixed first derivatives y'(0) and y'(n). */
101 #define SDERIV 2 /* Fixed second derivatives y''(0) and y''(n). */
102 #define PERIOD 3 /* Periodic: derivatives equal at end points. */
104 /* Token types for command line processing. */
109 /* Define error numbers. */
121 /* Constants and flags are global. */
122 int aflag
= FALSE
; /* Automatic abscissa flag. */
123 real step
= DEFSTEP
; /* Spacing for automatic abscissas. */
124 int bdcode
= EXTRAP
; /* Type of boundary conditions:
125 0 extrapolate f'' with coeficient k.
126 1 first derivatives given
127 2 second derivatives given
129 real leftd
= 0., /* Boundary values of derivatives. */
131 real k
= DEFBVK
; /* Boundary value constant. */
132 int rflag
= 0; /* 0: take range from data, 1: min. given, 2: min. & max. given. */
133 real xmin
= HUGE
, xmax
; /* Output range. */
134 unsigned nint
= NINT
; /* Number of intervals in output. */
135 unsigned maxpts
= NPTS
; /* Maximun number of points. */
136 unsigned nknots
= 0; /* Number of input points. */
137 int sflag
= FALSE
; /* If TRUE data must be sorted. */
138 char *datafile
; /* Name of data file */
140 int xcompare(); /* Function to compare x values of two data points. */
144 } *data
; /* Points to list of data points. */
147 double diag
, upper
, rhs
;
148 } *eqns
; /* Points to elements of band matrix used to calculate
149 coefficients of the splines. */
156 if (nknots
== 1) { /* Cannot interpolate with one data point. */
157 printf(OFMT
,data
->x
, data
->y
);
160 if (sflag
) /* Sort data if needed. */
161 qsort(data
, nknots
, sizeof(struct pair
), xcompare
);
166 /* xcompare -- Compare abcissas of two data points (for qsort). */
168 struct pair
*arg1
, *arg2
;
170 if (arg1
->x
> arg2
->x
)
172 else if (arg1
->x
< arg2
->x
)
178 /* error -- Print error message and abort fatal errors. */
185 "usage: spline [options] [file]\noptions:\n",
186 *options
= "-a spacing\n-k const\n-n intervals\n-m points\n-p\n-x xmin xmax\n";
188 fatal
= TRUE
; /* Default is fatal error. */
189 usemsg
= FALSE
; /* Default no usage message. */
190 fprintf(stderr
, "spline: ");
193 msg
= "not enough memory for %u data points\n";
196 msg
= "data file %s empty\n";
199 msg
= "unknown option: %c\n";
203 msg
= "cannot open file: %s\n";
206 msg
= "extra argument ignored: %s\n";
215 msg
= "duplicate abcissa value: %s\n";
218 msg
= "xmax < xmin not allowed %s\n";
220 /* The following errors "can't happen." */
221 /* If they occur some sort of bug is indicated. */
223 msg
= "singular matrix encountered %s\n";
226 msg
= "internal error: bad boundary value code %d\n";
229 fprintf(stderr
, "unknown error number: %d\n", errno
);
232 fprintf(stderr
, msg
, auxmsg
);
234 fprintf(stderr
,"%s%s", usage
, options
);
239 /* setup -- Initalize all constants and read data. */
244 FILE fdinp
, /* Source of input. */
247 fdinp
= doarg(argc
, argv
); /* Process command line arguments. */
249 /* Allocate memory for data and band matrix of coefficients. */
250 if ((data
= malloc(maxpts
*sizeof(struct pair
))) == NULL
) {
251 error(MEMERR
, (char *)maxpts
);
254 getdata(fdinp
); /* Read data from fdinp. */
256 fclose(fdinp
); /* Close input data file. */
258 error(NODATA
, datafile
);
260 /* Allocate memory for calculation of spline coefficients. */
261 if ((eqns
= malloc((nknots
+1)*sizeof(struct bandm
))) == NULL
) {
262 error(MEMERR
, (char *)nknots
);
266 /* doarg -- Process arguments. */
277 type
= gettok(&i
, &s
, argv
, str
);
279 if (type
== OPTION
) {
281 case 'a': /* Automatic abscissa. */
283 rflag
= rflag
< 2 ? 1 : rflag
;
284 if (xmin
== HUGE
) /* Initialize xmin, if needed. */
286 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
287 if ((step
= atof(str
)) <= 0.)
289 type
= gettok(&i
, &s
, argv
, str
);
292 case 'f': /* Fix first derivative at boundaries. */
294 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
296 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
298 type
= gettok(&i
, &s
, argv
, str
);
305 case 'k': /* Set boundary value constant. */
307 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
309 type
= gettok(&i
, &s
, argv
, str
);
312 case 'm': /* Set number of intervals in output. */
313 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
314 maxpts
= (unsigned)atoi(str
);
315 type
= gettok(&i
, &s
, argv
, str
);
318 case 'n': /* Set number of intervals in output. */
319 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
320 nint
= (unsigned)atoi(str
);
321 type
= gettok(&i
, &s
, argv
, str
);
324 case 'p': /* Require periodic interpolation function. */
326 type
= gettok(&i
, &s
, argv
, str
);
328 case 's': /* Fix second derivative at boundaries. */
330 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
332 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
334 type
= gettok(&i
, &s
, argv
, str
);
341 case 'x': /* Set range of x values. */
343 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
345 if ((type
= gettok(&i
, &s
, argv
, str
)) == NUMBER
) {
348 type
= gettok(&i
, &s
, argv
, str
);
355 error(BADOPT
, (char *)argv
[i
][1]);
361 if ((fdinp
= fopen(argv
[i
++], "r")) == NULL
) {
362 error(BADFILE
, argv
[i
-1]);
365 error(XTRAARG
, argv
[i
]);
375 /* gettok -- Get one token from command line, return type. */
376 gettok(indexp
, locp
, argv
, str
)
377 int *indexp
; /* Pointer to index in argv array. */
378 char **locp
; /* Pointer to current location in argv[*indexp]. */
382 char *strcpy(), *strchr();
386 while (isspace(*s
) || *s
== ',')
388 if (*s
== '\0') /* Look at next element in argv. */
390 if (*s
== '-' && isalpha(s
[1])) {
391 /* Found an option. */
397 else if (is_number(s
)) {
412 /* is_number -- Return TRUE if argument is the ASCII representation of a number. */
416 if (isdigit(*string
) ||
419 (*string
== '-' && (isdigit(string
[1]) || string
[1] == '.')))
425 /* getdata -- Read data points from fdinp. */
429 real lastx
, min
, max
;
431 char msg
[60], *realloc();
435 dp
= data
; /* Point to head of list of data. */
439 if (aflag
) { /* Generate abcissa. */
440 dp
->x
= xmin
+ nknots
*step
;
443 else { /* Read abcissa. */
444 while ((n
= (fscanf(fdinp
,IFMT
,&dp
->x
))) == 0)
445 ; /* Skip non-numeric input. */
452 if (lastx
> dp
->x
) { /* Check for monotonicity. */
453 sflag
= TRUE
; /* Data must be sorted. */
458 while ((n
= (fscanf(fdinp
, IFMT
, &dp
->y
))) == 0)
459 ; /* Skip non-numeric input. */
460 if (++nknots
>= maxpts
) { /* Too many points, allocate more memory. */
461 if ((data
= realloc(data
, (maxpts
*= 2)*sizeof(struct pair
))) == NULL
) {
462 error(MEMERR
, (char *)maxpts
);
465 sprintf(msg
, "more than %d input points, more memory allocated\n",
473 if (aflag
) { /* Compute maximum ordinate. */
474 max
= xmin
+ (nknots
-1)*step
;
483 /* calcspline -- Calculate coeficients of spline functions. */
490 /* calccoef -- Calculate coefficients of linear equations determining spline functions. */
493 struct bandm
*ptr
, tmp1
, tmp2
;
498 /* Initialize first row of matrix. */
499 if ((h1
= data
[1].x
- data
[0].x
) == 0.) {
500 sprintf(str
, "%8.5g", data
[0].x
);
503 switch(bdcode
) { /* First equation depends on boundary conditions. */
509 case FDERIV
: /* Fixed first derivatives at ends. */
512 h
= data
[1].x
- data
[0].x
;
513 ptr
->rhs
= 6.*((data
[1].y
- data
[0].y
)/(h
*h
) - leftd
/h
);
520 case PERIOD
: /* Periodic splines. */
525 error(BADBC
, (char *) bdcode
);
529 /* Initialize rows 1 to n-1, sub-diagonal band is assumed to be 1. */
530 for (i
=1; i
< nknots
-1; ++i
, ++ptr
) {
532 if ((h1
= data
[i
+1].x
- data
[i
].x
) == 0.) {
533 sprintf(str
, "%8.5g", data
[i
].x
);
536 ptr
->diag
= 2.*(h
+ h1
)/h
;
538 ptr
->rhs
= 6.*((data
[i
+1].y
-data
[i
].y
)/(h
*h1
) -
539 (data
[i
].y
- data
[i
-1].y
)/(h
*h
));
541 /* Initialize last row. */
543 case EXTRAP
: /* Standard case. */
545 ptr
->upper
= -k
; /* Upper holds actual sub-diagonal value. */
548 case FDERIV
: /* Fixed first derivatives at ends. */
551 h
= data
[nknots
-1].x
- data
[nknots
-2].x
;
552 ptr
->rhs
= 6.*((data
[nknots
-2].y
- data
[nknots
-1].y
)/(h
*h
) + rightd
/h
);
559 case PERIOD
: /* Use periodic boundary conditions. */
560 /* First and last row are not in tridiagonal form. */
561 h
= data
[1].x
- data
[0].x
;
562 h1
= data
[nknots
-1].x
- data
[nknots
-2].x
;
570 tmp2
.rhs
= 6.*((data
[1].y
- data
[0].y
)/(h
*h
) -
571 (data
[nknots
-1].y
- data
[nknots
-2].y
)/(h1
*h
));
572 /* Transform periodic boundary equations to tri-diagonal form. */
573 for (i
= 1; i
< nknots
- 1; ++i
) {
574 tmp1
.upper
/= tmp1
.diag
;
575 tmp1
.rhs
/= tmp1
.diag
;
576 ptr
->diag
/= tmp1
.diag
;
577 ptr
->upper
/= tmp1
.diag
;
578 tmp1
.diag
= tmp1
.upper
- eqns
[i
].diag
;
579 tmp1
.upper
= -eqns
[i
].upper
;
580 tmp1
.rhs
-= eqns
[i
].rhs
;
581 tmp2
.upper
/= tmp2
.diag
;
582 tmp2
.rhs
/= tmp2
.diag
;
583 eqns
->diag
/= tmp2
.diag
;
584 eqns
->upper
/= tmp2
.diag
;
585 tmp2
.diag
= tmp2
.upper
- eqns
[nknots
-1-i
].diag
/eqns
[nknots
-1-i
].upper
;
586 tmp2
.upper
= -1./eqns
[nknots
-1-i
].upper
;
587 tmp2
.rhs
-= eqns
[nknots
-1-i
].rhs
/eqns
[nknots
-1-i
].upper
;
589 /* Add in remaining terms of boundary condition equation. */
590 ptr
->upper
+= tmp1
.diag
;
591 ptr
->diag
+= tmp1
.upper
;
593 eqns
->diag
+= tmp2
.upper
;
594 eqns
->upper
+= tmp2
.diag
;
595 eqns
->rhs
= tmp2
.rhs
;
598 error(BADBC
, (char *) bdcode
);
602 /* solvband -- Solve band matrix for spline functions. */
612 /* Make a pass to triangularize matrix. */
613 for (i
=1; i
< nknots
- 1; ++i
, ++ptr
) {
614 if (fabs(ptr
->diag
) < EPS
) {
615 /* Near zero on diagonal, pivot. */
616 if (fabs(ptr
->upper
) < EPS
)
619 ptr
->diag
= i
; /* Keep row index in diag.
620 Actual value of diag is always 1. */
621 if (i
== nknots
- 2) {
623 /* Exchange next to last and last rows. */
624 k1
= ptr
->rhs
/ptr
->upper
;
625 if (fabs((ptr
+1)->upper
) < EPS
)
627 ptr
->rhs
= (ptr
+1)->rhs
/(ptr
+1)->upper
;
628 ptr
->upper
= (ptr
+1)->diag
/(ptr
+1)->upper
;
633 ptr
->rhs
= (ptr
+1)->rhs
- (k1
= ptr
->rhs
/ptr
->upper
)*(ptr
+1)->diag
;
634 ptr
->upper
= (ptr
+1)->upper
; /* This isn't super-diagonal element
635 but rather one to its right.
636 Must watch for this when
637 back substituting. */
642 (ptr
+1)->rhs
-= ptr
->rhs
;
646 ptr
->upper
/= ptr
->diag
;
647 ptr
->rhs
/= ptr
->diag
;
648 ptr
->diag
= i
-1; /* Used to reorder solution if needed. */
649 (ptr
+1)->diag
-= ptr
->upper
;
650 (ptr
+1)->rhs
-= ptr
->rhs
;
653 /* Last row is a special case. */
655 /* If flag == 2 last row is already computed. */
656 ptr
->upper
/= ptr
->diag
;
657 ptr
->rhs
/= ptr
->diag
;
658 ptr
->diag
= ptr
- eqns
;
660 ptr
->diag
-= (ptr
-1)->upper
* ptr
->upper
;
661 ptr
->rhs
-= (ptr
-1)->rhs
* ptr
->upper
;
662 ptr
->rhs
/= ptr
->diag
;
663 ptr
->diag
= ptr
- eqns
;
666 /* Now make a pass back substituting for solution. */
668 for ( ; ptr
>= eqns
; --ptr
) {
669 if ((int)ptr
->diag
!= ptr
- eqns
) {
670 /* This row and one above have been exchanged in a pivot. */
672 ptr
->rhs
-= ptr
->upper
* (ptr
+2)->rhs
;
675 ptr
->rhs
-= ptr
->upper
* (ptr
+1)->rhs
;
677 if (flag
) { /* Undo reordering done by pivoting. */
678 qsort(eqns
, nknots
, sizeof(struct bandm
), fcompare
);
682 /* fcompare -- Compare two floating point numbers. */
688 else if (arg1
< arg2
)
694 /* interpolate -- Do spline interpolation. */
699 real h
, xi
, yi
, hi
, xu
, xl
, limit
;
701 h
= (xmax
- xmin
)/nint
;
704 for (xi
= xmin
; xi
< xmax
+ 0.25*h
; xi
+= h
) {
705 while (dp
->x
< xi
&& dp
< data
+ nknots
- 1) {
706 ++dp
; /* Skip to correct interval. */
709 if (dp
< data
+ nknots
- 1 && dp
->x
< xmax
)
713 for ( ; xi
< limit
- 0.25*h
; xi
+= h
) {
714 /* Do interpolation. */
715 hi
= dp
->x
- (dp
-1)->x
;
718 yi
= ((ep
+1)->rhs
*xl
*xl
/(6.*hi
) + dp
->y
/hi
- (ep
+1)->rhs
*hi
/6.)*xl
+
719 (ep
->rhs
*xu
*xu
/(6.*hi
) + (dp
-1)->y
/hi
- ep
->rhs
*hi
/6.)*xu
;
720 printf(OFMT
, xi
, yi
);
722 if (limit
!= dp
->x
) { /* Interpolate. */
723 hi
= dp
->x
- (dp
-1)->x
;
725 xl
= xmax
- (dp
-1)->x
;
726 yi
= ((ep
+1)->rhs
*xl
*xl
/(6.*hi
) + dp
->y
/hi
- (ep
+1)->rhs
*hi
/6.)*xl
+
727 (ep
->rhs
*xu
*xu
/(6.*hi
) + (dp
-1)->y
/hi
- ep
->rhs
*hi
/6.)*xu
;
728 printf(OFMT
, xmax
, yi
);
730 else { /* Print knot. */
731 printf(OFMT
, dp
->x
, dp
->y
);