/* spline.c ********************************************************************************
* spline.c -- Routines for cubic spline curve objects
*
* User routines:
* Pro_SplineCurve(series) creates a new curve object by fitting
* cubic splines to the valid points of a
* given series.
*
* Mark Showalter, PDS Ring-Moon Systems Node, April 1998
*******************************************************************************/
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "profile.h"
#include "fortran.h"
/********************
* Type definitions *
********************/
typedef struct ZPRO_SPLINE_STRUCT {
XPRO_CLASS class;
RL_INT4 last;
RL_FLT8 *table0, *table2;
RL_FLT8 x1, x2, dx, k1;
} ZPRO_SPLINE;
/********************
* Static variables *
********************/
static XPRO_CLASS spline_class = {XPRO_SPLINE_CLASS, "cubic-spline", NULL};
/********************************
* Internal function prototypes *
********************************/
static RL_FLT8 ZPro_SplineValue RL_PROTO((RL_VOID *pointer, RL_FLT8 x,
RL_FLT8 *slope));
static RL_FLT8 ZPro_SplineInverse RL_PROTO((RL_VOID *pointer, RL_FLT8 y,
RL_FLT8 x1, RL_FLT8 x2));
static void ZPro_FreeSpline RL_PROTO((RL_VOID *pointer));
static void ZPro_PrintSpline RL_PROTO((RL_VOID *pointer));
/* Spline Toolkit prototypes
* Note that the entire source code for the Spline Toolkit has been appended
* below. The only change has been to "comment out" all of the #include
* directives, since they are handled above.
*/
RL_BOOL Spline_Setup RL_PROTO((RL_INT4 last, RL_BOOL ignore[],
RL_FLT8 y[], RL_FLT8 y2[]));
RL_FLT8 Spline_Value RL_PROTO((RL_FLT8 x, RL_INT4 last, RL_FLT8 y[],
RL_FLT8 y2[], RL_FLT8 *slope));
RL_FLT8 Spline_Invert RL_PROTO((RL_FLT8 value, RL_FLT8 xlo, RL_FLT8 xhi,
RL_INT4 last, RL_FLT8 y[], RL_FLT8 y2[],
RL_BOOL *error_flag));
RL_INT4 Spline_Extrema RL_PROTO((RL_FLT8 x1, RL_FLT8 x2,
RL_INT4 last, RL_FLT8 y[], RL_FLT8 y2[],
RL_INT4 maxcount, RL_FLT8 xlist[],
RL_INT4 types[]));
/*
********************************************************************************
* EXPORTED USER ROUTINES
********************************************************************************
*$ Component_name:
* Pro_SplineCurve (spline.c)
*$ Abstract:
* This routine generates a curve by fitting cubic splines to the samples
* of a series object.
*$ Keywords:
* PROFILE
* C, PUBLIC, SUBROUTINE
*$ Declarations:
* PRO_OBJECT *Pro_SplineCurve(series)
* PRO_OBJECT *series;
*$ Inputs:
* series pointer to a series object.
*$ Outputs:
* none
*$ Returns:
* pointer to a new spline curve object, or NULL on non-fatal error.
*$ Detailed_description:
* This routine generates a curve by fitting cubic splines to the samples
* of a series object. The domain and coordinate names of the new curve
* match those of the original series.
*
* A cubic spline curve has the property that it is everywhere smooth;
* its value and both its first and second derivatives are continuous.
* This is the optimal method for interpolating between samples of a
* smoothly varying function.
*$ External_references:
* Profile toolkit
*$ Side_effects:
* Memory is allocated.
*$ Examples:
* This snippet of code takes a series that contains only five valid
* samples, and uses it to construct a cubic spline function. Since the
* valid samples are all equal to x squared, the resultant spline function
* will return f(x)=x*x everywhere within the domain [-1,5].
*
* PRO_OBJECT *series, *curve;
* RL_FLT8 table[] = {-1., 0., 1., 4., 98., 99., 25.};
*
* series = Pro_ArraySeries(table, -1, 7, -1., 1.,
* 98., 99., TRUE, FALSE);
* curve = Pro_SplineCurve(series);
*$ Error_handling:
* Profile library error handling is in effect.
*
* Conditions raised:
* PRO_CLASS_ERROR if given object is NULL or is not a series.
* PRO_SETUP_FAILURE if insufficient valid values are found in the
* series object.
* RL_MEMORY_ERROR on memory allocation error.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: March 1998
*$ Change_history:
* none
*******************************************************************************/
PRO_OBJECT *Pro_SplineCurve(series)
PRO_OBJECT *series;
{
ZPRO_SPLINE *spline;
PRO_OBJECT *new;
RL_INT4 k, k1, k2, nsamples, flag, ix, xcount;
RL_BOOL status;
RL_BOOL *ignore = NULL;
RL_FLT8 *xlist = NULL;
/* Determine sample range in series */
Pro_SeriesIndices(series, &k1, &k2);
nsamples = k2 - k1 + 1;
/* Allocate data structure */
spline = (ZPRO_SPLINE *) XRL_Malloc(sizeof(ZPRO_SPLINE));
if (spline == NULL) return NULL;
spline->table0 = (RL_FLT8 *) XRL_Malloc(nsamples * sizeof(RL_FLT8));
spline->table2 = (RL_FLT8 *) XRL_Malloc(nsamples * sizeof(RL_FLT8));
if (spline->table2 == NULL) goto ERROR;
/* Initialize data structure */
spline->class = spline_class;
spline->last = nsamples - 1;
spline->k1 = (RL_FLT8) k1;
Pro_SeriesSampling(series, &(spline->x1), &(spline->x2), &(spline->dx));
/* Allocate temporary array for series flags */
ignore = (RL_BOOL *) XRL_Malloc(nsamples * sizeof(RL_BOOL));
if (ignore == NULL) goto ERROR;
/* Fill arrays for Spline_Setup() */
for (k = k1; k <= k2; k++) {
spline->table0[k - k1] = Pro_SeriesValue(series, k, &flag);
ignore[k - k1] = (flag != 0);
}
/* Set up spline tables (and override SPLINE_INSUFFICIENT_ARRAY error) */
(void) RL_ClearError1("SPLINE_INSUFFICIENT_ARRAY");
(void) RL_SetErrorType("SPLINE_INSUFFICIENT_ARRAY", RL_RECORD);
status = Spline_Setup(spline->last, ignore, spline->table0, spline->table2);
if (!status) {
if (RL_ClearError1("SPLINE_INSUFFICIENT_ARRAY")) {
(void) sprintf(xpro_message, "curve setup failure\n\
insufficient samples to set up cubic spline curve\n\
series \"%s\" contains fewer than four valid samples",
Pro_ObjectName(series,0));
RL_RaiseError("PRO_SETUP_FAILURE", xpro_message);
}
goto ERROR;
}
XRL_Free((RL_VOID *) ignore);
ignore = NULL;
/* Count and tabulate extrema */
xcount = Spline_Extrema(0., (RL_FLT8) spline->last, spline->last,
spline->table0, spline->table2, 0, NULL, NULL);
xlist = (RL_FLT8 *) XRL_Malloc(xcount * sizeof(RL_FLT8));
if (xcount > 0 && xlist == NULL) goto ERROR;
(void) Spline_Extrema(0., (RL_FLT8) spline->last, spline->last,
spline->table0, spline->table2, xcount, xlist, NULL);
for (ix = 0; ix < xcount; ix++) {
xlist[ix] = Pro_SeriesXValue(series, xlist[ix] + spline->k1);
}
/* Create curve object */
new = XPro_MakeCurve(spline->x1, spline->x2,
xcount, xlist, ZPro_SplineValue, ZPro_SplineInverse,
ZPro_FreeSpline, ZPro_PrintSpline,
(RL_VOID *) spline);
/* Free temporary arrays */
XRL_Free((RL_VOID *) ignore);
XRL_Free((RL_VOID *) xlist);
/* Enslave the series and check for error */
/* if (new != NULL && XPro_EnslaveObject(new, series)) {
Pro_FreeObject(new);
}
*/
/* Transfer coodinate names to new function */
Pro_RenameObject(new, 1, Pro_ObjectName(series,1));
Pro_RenameObject(new, 2, Pro_ObjectName(series,2));
return new;
ERROR:
XRL_Free((RL_VOID *) ignore);
XRL_Free((RL_VOID *) xlist);
ZPro_FreeSpline((RL_VOID *) spline);
return NULL;
}
/*
*******************************************************************************
* INTERNAL ROUTINES
********************************************************************************
* ZPro_SplineValue(pointer, x, slope)
*
* This internal routine calculates the value of a spline curve.
*
* Inputs:
* pointer pointer to the ZPRO_SPLINE data structure.
* x location at which to evaluate spline curve.
* *slope slope of spline curve at x.
*
* Return: value of spline curve at x.
*******************************************************************************/
static RL_FLT8 ZPro_SplineValue(pointer, x, slope)
RL_VOID *pointer;
RL_FLT8 x, *slope;
{
ZPRO_SPLINE *spline;
RL_FLT8 value;
spline = (ZPRO_SPLINE *) pointer;
value = Spline_Value((x - spline->x1) / spline->dx, spline->last,
spline->table0, spline->table2, slope);
if (slope != NULL) *slope /= spline->dx;
return value;
}
/*
*******************************************************************************
* ZPro_SplineInverse(pointer, y, x1, x2)
*
* This internal routine calculates the inverse of a spline curve
*
* Inputs:
* pointer pointer to the ZPRO_SPLINE data structure.
* y location at which to evaluate spline curve's inverse.
* x1, x2 limits on the search for the inverse.
*
* Return: location at which the value of the spline curve is y.
*
* Errors:
* PRO_EVAL_FAILURE if curve could not be inverted. In this case a
* guess at the nearest x within the specified
* bounds is returned.
*******************************************************************************/
static RL_FLT8 ZPro_SplineInverse(pointer, y, x1, x2)
RL_VOID *pointer;
RL_FLT8 y, x1, x2;
{
ZPRO_SPLINE *spline;
RL_FLT8 value;
RL_BOOL error_flag;
spline = (ZPRO_SPLINE *) pointer;
value = Spline_Invert(y, (x1 - spline->x1) / spline->dx,
(x2 - spline->x1) / spline->dx,
spline->last, spline->table0, spline->table2,
&error_flag);
if (error_flag) {
(void) sprintf(xpro_message, "function evaluation failure\n\
unable to invert cubic spline curve\n\
seeking f(x) = %#g where x is in [%#g,%#g]", y, x1, x2);
RL_RaiseError("PRO_EVALUATION_FAILURE", xpro_message);
}
return spline->dx * value + spline->x1;
}
/*
*******************************************************************************
* ZPro_FreeSpline(pointer)
*
* This internal routine frees the memory used by a ZPRO_SPLINE structure.
*
* Inputs:
* pointer pointer to the ZPRO_SPLINE data structure.
*******************************************************************************/
static void ZPro_FreeSpline(pointer)
RL_VOID *pointer;
{
ZPRO_SPLINE *spline;
spline = (ZPRO_SPLINE *) pointer;
XRL_Free((RL_VOID *) spline->table0);
XRL_Free((RL_VOID *) spline->table2);
XRL_Free(pointer);
}
/*
*******************************************************************************
* ZPro_PrintSpline(pointer)
*
* This internal routine prints out information about a cubic spline curve. It
* is called by Pro_PrintCurve() and is used mainly for debugging.
*
* Input:
* pointer pointer to the ZPRO_SPLINE structure.
*******************************************************************************/
static void ZPro_PrintSpline(pointer)
RL_VOID *pointer;
{
ZPRO_SPLINE *spline;
RL_INT4 i;
spline = (ZPRO_SPLINE *) pointer;
/* Make sure object is not NULL */
if (spline == NULL) {
printf("PRINT ERROR: Cubic spline curve pointer is NULL\n");
return;
}
/* Make sure object is a cubic spline curve */
if (spline->class.id != XPRO_SPLINE_CLASS) {
printf("PRINT ERROR: Object is not a cubic spline curve\n");
return;
}
/* Print object info... */
printf("\nCubic spline curve parameters...\n");
printf("elements = %1d\n", spline->last + 1);
printf("sampling = % #g, % #g, % #g\n", spline->x1, spline->x2,
spline->dx);
for (i = 0; i <= 5 && i <= spline->last; i++)
printf("series[%1d] = % #g, % #g\n", i,
spline->table0[i], spline->table2[i]);
return;
}
/*
********************************************************************************
* FORTRAN INTERFACE ROUTINES
********************************************************************************
*$ Component_name:
* FPro_SplineCurve (spline.c)
*$ Abstract:
* This routine generates a curve by fitting cubic splines to the samples
* of a series object.
*$ Keywords:
* PROFILE
* FORTRAN, PUBLIC, SUBROUTINE
*$ Declarations:
* integer*4 function FPro_SplineCurve(series)
* integer*4 series
*$ Inputs:
* series FORTRAN pointer to a series object.
*$ Outputs:
* none
*$ Returns:
* pointer to a new spline curve object, or 0 on non-fatal error.
*$ Detailed_description:
* This routine generates a curve by fitting cubic splines to the samples
* of a series object. The domain and coordinate names of the new curve
* match those of the original series.
*
* A cubic spline curve has the property that it is everywhere smooth;
* its value and both its first and second derivatives are continuous.
* This is the optimal method for interpolating between samples of a
* smoothly varying function.
*$ External_references:
* Profile toolkit
*$ Side_effects:
* Memory is allocated.
*$ Examples:
* This snippet of code takes a series that contains only five valid
* samples, and uses it to construct a cubic spline function. Since the
* valid samples are all equal to x squared, the resultant spline function
* will return f(x)=x*x everywhere within the domain [-1,5].
*
* integer*4 series, curve
* real*8 table(7)/ -1.d0, 0.d0, 1.d0, 4.d0, 98.d0, 99.d0, 25.d0/
*
* series = Pro_ArraySeries(table, -1, 7, -1.d0, 1.d0,
* & 98.d0, 99.d0, .TRUE., .FALSE.)
* curve = FPro_SplineCurve(series)
*$ Error_handling:
* Profile library error handling is in effect.
*
* Conditions raised:
* PRO_CLASS_ERROR if given object is NULL or is not a series.
* PRO_SETUP_FAILURE if insufficient valid values are found in the
* series object.
* RL_MEMORY_ERROR on memory allocation error.
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* NASA/Ames Research Center
*$ Version_and_date:
* 1.0: March 1998
*$ Change_history:
* none
*******************************************************************************/
RL_INT4 FORTRAN_NAME(fpro_splinecurve) (series)
RL_INT4 *series;
{
RL_VOID *ptr1, *ptr2;
RL_INT4 index;
/* Look up series pointer */
ptr1 = FORT_GetPointer(*series);
if (ptr1 == NULL) return 0;
/* Call function */
ptr2 = (RL_VOID *) Pro_SplineCurve((PRO_OBJECT *) ptr1);
if (ptr2 == NULL) return 0;
/* Save new pointer */
index = FORT_AddPointer(ptr2);
if (index == 0) Pro_FreeObject((PRO_OBJECT *) ptr2);
return index;
}
/*
********************************************************************************
* CUBIC SPLINE TOOLKIT
********************************************************************************
*$ Component_name:
* Spline_Setup (spline.c)
*$ Abstract:
* Fills an array with second derivative estimates for use in cubic spline
* interpolation.
*$ Keywords:
* PROFILE, NUMERICAL_ANALYSIS, SPLINE
* C, PUBLIC, SUBROUTINE
*$ Declarations:
* RL_BOOL Spline_Setup(last, ignore, y, y2)
* RL_INT4 last;
* RL_BOOL ignore;
* RL_FLT8 y[], y2[];
*$ Inputs:
* last index for the last tabulated value. Note that this is
* equal to the number of intervals between tabulated
* values, and is one less than the number of tabulated
* values.
* ignore[] TRUE to indicate that the corresponding y-value is
* invalid and should be ignored. You may pass a NULL
* pointer to indicate all y-values are valid.
* y[] tabulated y-values (which must be uniformly spaced).
*$ Outputs:
* y[] any y-values flagged with ignore[] == TRUE are filled in
* with an interpolated value.
* y2[] table of second derivatives, scaled by a factor to
* facilitate interpolation.
*$ Returns:
* TRUE if spline setup was successful; FALSE if an error occurred.
*$ Detailed_description:
* This function fills an array with second derivative estimates for use in
* determining cubic splines. These values may then be used as an input to
* function Spline_Value() for determining interpolated values. This
* interpolation scheme insures that a function and its first and second
* derivatives are everywhere continuous.
*
* In this implementation, the tabulated values must be uniformly spaced in
* the independent variable x. We treat this spacing as unity, which
* results in no loss of generality. The implementation does permit
* embedded invalid values (indicated by TRUE values in the ignore[]
* array); these values are then filled in by cubic spline interpolation so
* that the returned array contains only valid values.
*
* The procedure employed is documented within the code. It is based on
* the algorithm found in:
* Hornbeck, Robert W. Numerical Methods. Quantum Publishers,
* Inc., New York, 1975, pp. 47--50.
* The procedure is quite similar to that found in Numerical Recipes
* subroutine SPLINE, for the special case where x-values are uniformly
* spaced. See:
* Press, William H., et al. Numerical Recipes. Cambridge
* University Press, Cambridge, 1986, pp. 86--89.
*$ External_references:
* none
*$ Side_effects:
* A temporary data array is allocated and then deallocated.
*$ Examples:
* none
*$ Error_handling:
* This routine returns FALSE if an error occurred. It also uses standard
* RingLib error handling. Conditions raised:
*
* RL_MEMORY_ERROR on memory allocation failure.
* SPLINE_INSUFFICIENT_ARRAY if the y-array contains fewer than 4 valid
* values.
*$ Limitations:
* As noted above, the tabulated values must be uniformly spaced in the
* independent variable x. A minimum of four tabulated values must be
* given.
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node, NASA/Ames Research Center
*$ Version_and_date:
* 1.0: October 1996
*$ Change_history:
* none
*******************************************************************************/
/*
#include <stdlib.h>
#include "spline.h"
#include "rl_memory.h"
#include "rl_error.h"
*/
RL_BOOL Spline_Setup(last, ignore, y, y2)
RL_INT4 last;
RL_BOOL ignore[];
RL_FLT8 y[], y2[];
{
RL_INT4 *i, j, ii, i1, i2, n;
RL_BOOL hasgaps;
RL_FLT8 *coeff, *rhval, dxj0, dxj1, dyj0, dyj1, a, b, r,
x, ytemp[2], y2temp[2];
/* Allocate necessary arrays */
i = (RL_INT4 *) XRL_Malloc((last+1) * sizeof(RL_INT4));
coeff = (RL_FLT8 *) XRL_Malloc((last+1) * sizeof(RL_FLT8));
rhval = (RL_FLT8 *) XRL_Malloc((last+1) * sizeof(RL_FLT8));
if (rhval == NULL) {
XRL_Free((RL_VOID *) i);
XRL_Free((RL_VOID *) coeff);
return FALSE;
}
/*******************************************************************************
* We assume that our sequence of values y[i] is uniformly spaced in x but that
* some points may be missing or invalid, as indicated by a non-zero value for
* ignore[i]. As a first step, we derive an index array i[j] to convert to a
* non-uniform sequence in which all values are valid.
*******************************************************************************/
/* Set up index table that skips over invalid entries */
hasgaps = FALSE;
j = 0;
for (ii=0; ii<=last; ii++) {
if (ignore != NULL && ignore[ii]) {
hasgaps = TRUE;
} else {
i[j] = ii;
j++;
}
}
n = j;
/* Check for valid number of entries */
if (n < 4) {
RL_RaiseError("SPLINE_INSUFFICIENT_ARRAY",
"spline array has insufficient valid values");
XRL_Free((RL_VOID *) i);
XRL_Free((RL_VOID *) coeff);
XRL_Free((RL_VOID *) rhval);
return FALSE;
}
/*******************************************************************************
* Cubic splines require tabulations of the function's second derivative y''
* in addition to the function's value y. The requirement that first derivatives
* be continuous leads to a sequence of coupled equations:
* dx[j] y''[j-1] + 2 (dx[j+1]+dx[j]) y''[j] + dx[j+1] y''[j+1]
* = 6 (dy[j+1]/dx[j+1] - dy[j]/dx[j])
* where
* dx[j] = x[j] - x[j-1];
* dy[j] = y[j] - y[j-1]
* (Press et al., eq. (3.3.7), p. 87). In addition, we use the following
* boundary conditions at the end points:
* y''[0] = y''[1]
* y''[N-2] = y''[N-1]
* These guarantee that the interpolation will match a polynomial up to second
* order exactly.
*
* Instead of returning the actual second derivatives yy'', we return the values
* divided by 6; this makes the interpolation function Spline_Value() faster.
*******************************************************************************/
/* Set up coefficients for first constraint based on the boundary condition:
* y''[0] + coeff[0] y''[1] = rhval[0]
*/
coeff[0] = -1.;
rhval[0] = 0.;
/* Set up coefficients for each new constraint in sequence... */
dxj1 = i[1] - i[0];
dyj1 = y[i[1]] - y[i[0]];
for (j=1; j < n-1; j++) {
/* Calculate differences */
dxj0 = dxj1;
dyj0 = dyj1;
dxj1 = i[j+1] - i[j];
dyj1 = y[i[j+1]] - y[i[j]];
/* Set up coefficients for next constraint:
* dx[j] y''[j-1] + 2 (dx[j+1]+dx[j]) y''[j] + dx[j+1] y''[j+1]
* = (dy[j+1]/dx[j+1] - dy[j]/dx[j])
* rewritten as...
* y''[j-1] + a y''[j] + b y''[j+1] = r[j]
*/
a = 2 * (dxj1 + dxj0) / dxj0;
b = dxj1 / dxj0;
r = (dyj1/dxj1 - dyj0/dxj0) / dxj0;
/* Now subtract off previous constraint and then rescale
* y''[j-1] + a y''[j] + b y''[j+1] = r
* - y''[j-1] + coeff[j-1] y''[j] = rhval[j-1]
* ------------------------------------------------------------------
* (a - coeff[j-1]) y''[j] + b y''[j+1] = (r - rhval[j-1])
*/
coeff[j] = b / (a - coeff[j-1]);
rhval[j] = (r - rhval[j-1]) / (a - coeff[j-1]);
}
/* At this point we have N-1 equations in N unknowns, of the form:
* y''[j] + coeff[j] y''[j+1] = rhval[j]
* for j = 0 to N-2.
*
* Use the last boundary condition to solve for y''[N-1]
* y''[N-2] + coeff[N-2] y''[N-1] = rhval[N-2]
* y''[N-2] + (-1) y''[N-1] = 0
*/
y2[i[n-1]] = rhval[n-2] / (coeff[n-2] + 1.);
/* Now back-substitute to solve for remaining values */
for (j = n-2; j >= 0; j--) {
y2[i[j]] = rhval[j] - coeff[j] * y2[i[j+1]];
}
/*******************************************************************************
* Now we have to fill in the missing points so the returned array has no
* invalid values.
*******************************************************************************/
if (hasgaps) {
j = 1;
for (ii = 0; ii <= last; ii++) {
if (ignore[ii]) {
while (i[j] < ii && j < n-1) j++;
i1 = i[j-1];
i2 = i[j];
ytemp[0] = y[i1];
ytemp[1] = y[i2];
y2temp[0] = y2[i1];
y2temp[1] = y2[i2];
x = (ii - i1) / ((RL_FLT8) (i2 - i1));
y[ii] = Spline_Value(x, 1, ytemp, y2temp, NULL);
y2[ii] = y2temp[0] + x * (y2temp[1] - y2temp[0]);
}
}
}
/* Free the temporary arrays */
XRL_Free((RL_VOID *) i);
XRL_Free((RL_VOID *) coeff);
XRL_Free((RL_VOID *) rhval);
return TRUE;
}
/*
********************************************************************************
*$ Component_name:
* Spline_Value (spline.c)
*$ Abstract:
* Returns a function value and (optionally) the local derivative based on
* cubic spline interpolation.
*$ Keywords:
* PROFILE, NUMERICAL_ANALYSIS, SPLINE
* C, PUBLIC, SUBROUTINE
*$ Declarations:
* RL_FLT8 Spline_Value(x, last, y, y2, slope)
* RL_FLT8 x, y[], y2[], *slope;
* RL_INT4 last;
*$ Inputs:
* x location at which to interpolate, assuming that the
* tabulated values start at x=0 and are spaced by dx=1.
* last last index into the array, equal to one less than the
* dimension of y0[] and y2[].
* y0[0...last] table of y-values.
* y2[0...last] table of second derivatives returned by Spline_Setup().
* slope a value of NULL will suspend the calculation of the
* tabulated function's derivative.
*$ Outputs:
* *slope first derivative of the tabulated function at the point
* selected. (Not calculated if slope==NULL). Note that
* this value is based upon a spacing of unity between
* tabulated values; it should be divided by dx if (dx!=1).
*$ Returns:
* interpolated value of the function.
*$ Detailed_description:
* This function returns a function value and (optionally) the local
* derivative based on cubic spline interpolation. It requires a set of
* tabulate y-values, along with the second derivatives of y as returned by
* Spline_Setup(). The returned value is an extrapolation from the
* endpoints when x is outside the table limits.
*
* The procedure employed is based on the algorithm found in:
* Hornbeck, Robert W. Numerical Methods. Quantum Publishers,
* Inc., New York, 1975, pp. 47--50.
* It is quite similar to that found in Numerical Recipes subroutine SPLINT
* for the special case where x-values are uniformly spaced. See:
* Press, William H., et al. Numerical Recipes. Cambridge
* University Press, Cambridge, 1986, pp. 86--89.
*$ External_references:
* none
*$ Side_effects:
* none
*$ Examples:
* none
*$ Error_handling:
* none
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node, NASA/Ames Research Center
*$ Version_and_date:
* 1.0: October 1996
*$ Change_history:
* none
*******************************************************************************/
/*
#include <math.h>
#include "spline.h"
*/
RL_FLT8 Spline_Value(x, last, y, y2, slope)
RL_FLT8 x, y[], y2[], *slope;
RL_INT4 last;
{
RL_INT4 index;
RL_FLT8 xlo, xhi, xlo2, xhi2, value;
index = (RL_INT4) floor(x);
if (index < 0) index = 0;
if (index >= last) index = last - 1;
y += index;
y2 += index;
xlo = x - index;
xhi = 1. - xlo;
/*******************************************************************************
* The equations below simplify for the case y2[0] == y2[1]. This situation
* arises frequently because the cubic spline solution forces this condition at
* the end points. A simplified form of the solution avoids some loss of
* precision for x values far outside the range (0...last).
*******************************************************************************/
if (y2[0] != y2[1]) {
xlo2 = xlo * xlo;
xhi2 = xhi * xhi;
value = xhi*y[0] + xlo*y[1] +
xhi*(xhi2 - 1.)*y2[0] + xlo*(xlo2 - 1.)*y2[1];
if (slope != NULL)
*slope = y[1] - y[0] +
(3.*xlo2 - 1.)*y2[1] - (3.*xhi2 - 1.)*y2[0];
}
else {
value = xhi*y[0] + xlo*(y[1] + 3.*(xlo - 1.)*y2[0]);
if (slope != NULL)
*slope = y[1] - y[0] + (6.*xlo - 3.)*y2[0];
}
return value;
}
/*
********************************************************************************
*$ Component_name:
* Spline_Invert (spline.c)
*$ Abstract:
* Finds the index that maps to a particular tabulated spline value.
*$ Keywords:
* PROFILE, NUMERICAL_ANALYSIS, SPLINE
* C, PUBLIC, SUBROUTINE
*$ Declarations:
* RL_FLT8 Spline_Invert(value, xlo, xhi, last, y, y2, error_flag)
* RL_FLT8 value, xlo, xhi, y[], y2[];
* RL_INT4 last;
* RL_BOOL *error_flag;
*$ Inputs:
* value spline y-value for which the corresponding index is
* sought.
* xlo lower bound on the desired solution, or -HUGE_VAL for an
* unbounded search.
* xhi upper bound on the desired solution, or HUGE_VAL for an
* unbounded search.
* y[0...last] table of y-values as passed to Spline_Setup().
* y2[0...last] table of second derivatives as returned by
* Spline_Setup().
*$ Outputs:
* *error_flag TRUE if spline could not be inverted; FALSE otherwise.
*$ Returns:
* the index value that maps nearest to the given spline y-value.
*$ Detailed_description:
* This function returns the index that maps nearest to a particular
* tabulated spline value, within specified limits. It is the inverse of
* function Spline_Value(). The solution is found by Newton's Method.
*$ External_references:
* Spline_Value().
*$ Side_effects:
* none
*$ Examples:
* none
*$ Error_handling:
* *error_flag is set to TRUE if a solution was not found.
*$ Limitations:
* The nearest endpoint will be returned if the y-value sought does not
* fall within the selected limits. Convergence is not guaranteed if the
* spline is not monotonic between the given limits. Also, one has no
* control over which occurrence of the y-value is returned when more than
* one solution is present.
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node, NASA/Ames Research Center
*$ Version_and_date:
* 1.0: October 1996
*$ Change_history:
* none
*******************************************************************************/
/*
#include <math.h>
#include "spline.h"
*/
#define SMALL_STEP 1.e-8 /* When steps drop below this cutoff, we will
consider an increase in the magnitude of the
step to be considered evidence for
convergence. This guarantees termination of
the loop. */
RL_FLT8 Spline_Invert(value, xlo, xhi, last, y, y2, error_flag)
RL_FLT8 value, xlo, xhi, y[], y2[];
RL_INT4 last;
RL_BOOL *error_flag;
{
RL_FLT8 ylo, yhi, xtest, ytest, dydx, resid, dx, absdx, prev_absdx;
RL_INT4 was_xlo, was_xhi;
/* Anticipate success */
*error_flag = FALSE;
/* Test for valid boundaries */
if (xlo >= xhi) {
*error_flag = TRUE;
return xlo;
}
/* Take an initial guess at x by linear interpolation between endpoints.
* However, for semi-bounded searches, just start near the finite limit; for
* completely unbounded searches, use linear interpolation between tabulated
* endpoints. */
if (xlo != -HUGE_VAL) {
if (xhi != HUGE_VAL) {
ylo = Spline_Value(xlo, last, y, y2, NULL);
yhi = Spline_Value(xhi, last, y, y2, NULL);
xtest = xlo + (value - ylo) * (xhi-xlo)/(yhi-ylo);
}
else {
xtest = xlo + last;
}
}
else {
if (xhi != HUGE_VAL) {
xtest = xhi - last;
}
else {
xtest = last * (value - y[0]) / (y[last] - y[0]);
}
}
/* Initialize flags to indicate when we hit bounds */
was_xlo = FALSE;
was_xhi = FALSE;
/* Avoid termination on first pass */
absdx = SMALL_STEP;
/*******************************************************************************
* Iterate using Newton's Method
*******************************************************************************/
while (1) {
/* Make sure x falls within bounds */
if (xtest < xlo) {
if (was_xlo) {
*error_flag = TRUE;
return xlo;
}
was_xlo = TRUE;
xtest = xlo;
}
if (xtest > xhi) {
if (was_xhi) {
*error_flag = TRUE;
return xhi;
}
was_xhi = TRUE;
xtest = xhi;
}
/* Evaluate spline function and derivative */
ytest = Spline_Value(xtest, last, y, y2, &dydx);
/* Simplest test for convergence */
resid = ytest - value;
if (resid == 0.) return xtest;
/* At local extremum, jump beyond more distant endpoint and try again */
if (dydx == 0.) {
if (xtest - xlo < xhi - xtest) xtest = xhi + 1.;
else xtest = xlo - 1.;
continue;
}
/* Generate next guess */
dx = resid / dydx;
xtest -= dx;
/* If changes stop shrinking, we have convergence */
prev_absdx = absdx;
absdx = fabs(dx);
if (absdx < SMALL_STEP && absdx >= prev_absdx) return xtest;
}
}
/*
********************************************************************************
*$ Component_name:
* Spline_Extrema (spline.c)
*$ Abstract:
* Locates all of the extrema within a cubic spline function.
*$ Keywords:
* PROFILE, NUMERICAL_ANALYSIS, SPLINE
* C, PUBLIC, SUBROUTINE
*$ Declarations:
* RL_INT4 Spline_Extrema(x1, x2, last, y, y2,
* maxcount, xlist, types)
* RL_INT4 last, maxcount, types[];
* RL_FLT8 x1, x2, y[], y2[], xlist[]
*$ Inputs:
* x1, x2 range of x-values within which to locate all extrema.
* last last index of y and y2 tables (one less than number of
* elements).
* y[0...last] table of y-values.
* y2[0...last] table of second derivatives, returned by Spline_Setup().
* maxcount dimensioned number of available elements in xlist[] and
* types[].
* xlist buffer where extremum locations should be placed, or
* NULL to suspend return of locations.
* types buffer where extremum types should be placed, or NULL
* to suspend return of types.
*$ Outputs:
* xlist[0...] locations of all extrema, up to first maxcount.
* types[0...] -1 for minima; 0 for inflection points; +1 for maxima.
*$ Returns:
* The number of local extrema within the interval of interest (which may
* be > maxcount).
*$ Detailed_description:
* This function locates the extrema within a single interval of cubic
* spline interpolation. Note that the interval includes the lower
* endpoint but not the upper endpoint. Inflection points are ignored.
*$ External_references:
* none
*$ Side_effects:
* none
*$ Examples:
* To locate and save all the extrema in a spline function...
*
* total = Spline_Extrema(-HUGE_VAL, HUGE_VAL, y, y2, 0, NULL, NULL);
* xlist = (RL_FLT8) XRL_Malloc(total * sizeof(RL_FLT8));
* types = (RL_INT4) XRL_Malloc(total * sizeof(RL_INT4));
* Spline_Extrema(-HUGE_VAL, HUGE_VAL, y, y2, total, xlist, types);
*$ Error_handling:
* none
*$ Limitations:
* none
*$ Author_and_institution:
* Mark R. Showalter
* PDS Ring-Moon Systems Node, NASA/Ames Research Center
*$ Version_and_date:
* 1.0: January 1997
*$ Change_history:
* none
*******************************************************************************/
/*
#include <math.h>
#include "spline.h"
*/
#define ISIGN(x) (x < 0. ? -1 : (x == 0. ? 0 : 1))
RL_INT4 Spline_Extrema(x1, x2, last, y, y2, maxcount, xlist, types)
RL_INT4 last, maxcount, types[];
RL_FLT8 x1, x2, y[], y2[], xlist[];
{
RL_INT4 count, i1, i2, imin, imax, nx, j, type;
RL_FLT8 a, b, c, discr, q, xs[2], temp, xtest, xtrue, xprev, second;
/*******************************************************************************
* Loop through all relevant intervals of spline
*******************************************************************************/
xprev = -HUGE_VAL;
count = 0;
imin = (x1 < 1.) ? 0 : (RL_INT4) floor(x1);
imax = (x2 > last) ? last : (RL_INT4) ceil(x2);
for (i1 = imin; i1 < imax; i1++) {
i2 = i1 + 1;
/*******************************************************************************
* Locate the extrema
*
* We wish to solve for the x-values at which the slope of the cubic spline is
* zero. We have
* slope = y[i2] - y[i1] +
* (3*x*x - 1)*y2[i2] - (3*(1-x)^2 - 1)*y2[i1]
* This can be solved via the quadratic formula.
*******************************************************************************/
/* Generate quadratic coefficients */
a = 3. * (y2[i2] - y2[i1]);
b = 6. * y2[i1];
c = y[i2] - y[i1] - y2[i2] - 2.*y2[i1];
/* Solve quadratic formula (See Numerical Methods, p. 145) */
nx = 0;
discr = b*b - 4.*a*c;
if (discr >= 0.) {
if (b > 0.) q = -0.5 * (b + sqrt(discr));
else q = -0.5 * (b - sqrt(discr));
if (q != 0.) {xs[nx] = c/q; nx++;}
if (a != 0.) {xs[nx] = q/a; nx++;}
}
/* Put pair of solutions in increasing order and check for duplicates */
if (nx == 2) {
if (xs[0] == xs[1]) {
nx = 1;
}
else if (xs[0] > xs[1]) {
temp = xs[0];
xs[0] = xs[1];
xs[1] = temp;
}
}
/*******************************************************************************
* Test each extremum
*******************************************************************************/
for (j=0; j<nx; j++) {
/* Exclude extrema outside the interval */
xtest = xs[j];
if (xtest < 0. && i1 != 0) continue;
if (xtest > 1. && i2 != last) continue;
xtrue = xtest + i1;
if (xtrue < x1) continue;
if (xtrue > x2) continue;
if (xtrue <= xprev) continue;
/* Test for minimum, maximum or inflection */
second = (a+a)*xtest + b;
type = -ISIGN(second);
/* When an inflection point falls on an interval boundary, we can look
at neighboring intervals for more information */
if (type == 0) {
if (xtrue == i1 && i1 > 0 &&
ISIGN(y2[i1-1]) * ISIGN(y2[i2]) == 1)
type = -ISIGN(y2[i1-1]);
if (xtrue == i2 && i2 < last &&
ISIGN(y2[i1]) * ISIGN(y2[i2+1]) == 1)
type = -ISIGN(y2[i2+1]);
}
/* Save this extremum in list, if possible */
if (count < maxcount) {
if (xlist != NULL) xlist[count] = xtrue;
if (types != NULL) types[count] = type;
}
count++;
xprev = xtrue;
}
}
return count;
}
/*******************************************************************************
*/