MODEL:
! LINGO model to fit a spline interpolation curve to a set of points. 
 Roughly speaking, find the smoothest curve that goes through
 all the given (x, y) points. In any interval i, between two adjacent 
 given points, a cubic polynomial is derived to 
 interpolate between the two points.
 Inputs:
    Data: 
      1) NOBS = number of observations,
      2) X(i)  for i = 1, 2, ..., N,for each observation,
          The X values of the set of points.
          These should be in increasing order.
      3) Y(i) for i = 1, 2, ..., N, .
          The Y values of the set of points.
    Outputs:
     5) NERR = error code,  = 0 if all OK, >0 if error, e.g.,
         points are not in increasing order.
     6) A(i) for i = 1, 2, ..., N-1,
     7) B(i) for i = 1, 2, ..., N-1,
     8) C(i) for i = 1, 2, ..., N-1,
     9) D(i) for i = 1, 2, ..., N-1,

   Details:
    Given a specific XS, X(i)<= XS <= X(i+1),
     the predicted(or corrected or adjusted) estimate of Y is given by:
    delta = XS - X(i1),
    Predict = D(i1)*delta^3 + C(i1)*delta^2 + B(i1)*delta + A(i1),
 
  Roughly speaking, a spline, Y = F(X),  is the "smoothest possible" curve that 
    a) passes through each of a given set of points. I.e., If X is
       the measured value at one of the given points, then
       F(X) = the true value at that point.
    b) is continuous everywhere, i.e., F(X) has no jumps.
    c) is smooth, i.e., the first derivative is continuous,-
        it has no abrupt changes, F(x) has no sharp changes.
    d) the second derivative is continuous everywhere, e.g.,
       F(x) is not "curvy" in one interval and then abruptly
       straight in the next interval ;

! Keywords: Spline fit, Interpolation;
SETS:
 OBS: Y,  X, A, B, C, D, DELTA, ABS2;
ENDSETS
DATA: ! Here are the calibration data points where we have observations on Y = F(X); ! X = 1 2 3 4 5; ! Y = 1 3 2 5 4; ! Case: Twin Peaks; X = 1 2 3 4 5; Y = 1 3 2 3 1; ! Case: Quadratic; ! X = 1 2 3 4 5; ! Y = 1 4 9 16 25; ! Case: Step function; ! X = 2 3 4 5 6 7; ! Y = 1 1 1 2 2 2; ! Case: sin(x); ! X = 1 2 3 4 5 6 7 8 9; ! Y = 0.84147 0.90923 0.14112 -0.75680 -0.95892 -0.27942 0.65699 0.98936 0.41212; ! Case: Industrial measurement calibration data 1; ! X = 6.5 16.16378 36.20551 54.07717 71.98346; ! Y = 6.5 16.128 36.154 54.022 72.012 ; ! Case: Industrial measurement calibration data 2; ! X = 6.5 13 21.5 32.5 65; ! Y = 6.5 13 33.5 47.5 58.5; ! Internal parameters; ! Set TESTON > 0 if want code to generate output, else 0; TESTON = 1; ! Wgt to be applied to minimizing sum of second derivatives, in case there are alternate optima; WGTSUM = 0.1; ENDDATA SUBMODEL FitSpline: ! This presumes we know the Y and want to predict X. ! Fit a cubic spline through the points.; @FOR( OBS(i): @FREE( A(i)); @FREE( B(i)); @FREE( C(i)); @FREE( D(i)); ); ! There are 4*(NOBS-1) parameters to be estimated; @FOR( OBS(i) | i #LT# NOBS: ! The curve in interval i must pass through X(i), Y(i); [PASSTHRU] A(i) = Y(i); ! This fixes NOBS -1 of the paramaters; ! The curve must be continuous, i.e., in interval i must pass through X(i+1), Y(i+1), This gives NOBS-1 more equations; [CONT0] D(i)*DELTA(i)^3 + C(i)*DELTA(I)^2 + B(i)*DELTA(i) + A(i) = Y(i+1); ! Curve must be smooth, i.e., continuous first derivative at i+1, (this gives NOBS-2 more equations, note B(NOBS) is free), so; [CONT1] 3*D(i)*DELTA(i)^2 + 2*C(i)*DELTA(i) + B(i) = B(i+1); ! To avoid abrupt changes in curviness, we also want, continuous 2nd derivative at i+1, (this gives NOBS-2 more equations, note C(NOBS) is free) so ; [CONT2] 6*D(i)*DELTA(i) + 2*C(i) = 2*C(i+1); ); ! One possible boundary restriction is that second derivatives be 0 at the end points... ( giving 4*(NOBS-1) linear equations in 4*(NOBS-1) variables; C(1) = 0; 2*C( NOBS-1) + 6*D( NOBS-1)* DELTA( NOBS-1) = 0; ! Look at maximumum absolute second derivative; @FOR( OBS(i) | i #LT# NOBS: ! The maximum in an interval occurs at one of the end points; ABS2(i) >= 2*C(i); ABS2(i) >= -2*C(i); ABS2(i) >= 2*C(i) + 6*D(i)*DELTA(i); ABS2(i) >= -(2*C(i) + 6*D(i)*DELTA(i)); MXABS2 >= ABS2(i); ); ! Minimize the maximum second derivative; MIN = MXABS2; ENDSUBMODEL
PROCEDURE EvalCurves: ! Input: XS = a specific X value, Output: YL = corresponding Y value, using linear interpolation, YC = corresponding Y value, using cubic spline interpolation; ! Find the interval containing XS; i1 = 1; @FOR( OBS(i) | i #GT# 1 #AND# X(i) #LT# XS: i1 = i1 + 1; ); ! If falls above highest point, we extraplate using the highest interval; @IFC( i1 #GE# NOBS: i1 = NOBS-1); ! Similarly, if falls below lowest point, we extrapolate using the lowest interval. ! We use the interval X(i1), X(i1+1); ! Get distance above low end of interval; XDEL = XS - X(i1); YL = (Y(i1)*(X(i1+1)- XS) + Y(i1+1)*(XS - X(i1)))/(X(i1+1)- X(i1)); YC = D(i1)*XDEL^3 + C(i1)*XDEL^2 + B(i1)*XDEL + A(i1); ENDPROCEDURE CALC: @SET('TERSEO',3); ! Turn off all output; ! Check input for errors; NERR = 0; NOBS = @SIZE( OBS); @FOR( OBS(i) | i #GT# 1: ! @IFC( Y(i) #LE# Y(I-1): NERR = 1); @IFC( X(i) #LE# X(I-1): NERR = 1;); ! We expect the X(i)'s ordered; DELTA( i-1) = X(i) - X(i-1); ! Compute length of each interval; ); ! @GEN( FitSpline); ! Fit a spline; @SOLVE( FitSpline); @IFC( TESTON: @WRITE(' A simple report of the prediction coefficients', @NEWLINE(1)); @WRITE(' Interval D(i) C(i) B(i) A(i)', @NEWLINE(1)); @FOR( OBS(i) | i #LT# NOBS: @WRITE( @FORMAT(i,'4.0F'),' ', @FORMAT( D(i),'10.6F'),' ', @FORMAT(C(i),'10.6F'),' ', @FORMAT(B(i),'10.6F'),' ',@FORMAT(A(i),'10.5F'), @NEWLINE(1)); ); @WRITE(@NEWLINE(1)); ); XLO = @MIN( OBS(i): X(i)); XHI = @MAX( OBS(i): X(I)); ! Plot some curves; @CHARTPCURVE( 'Fitting a Line Through Points', 'x-axis', 'y-axis', EvalCurves, XS, XLO, XHI, 'Piecewise Linear', YL, 'Cubic Spline', YC ); ENDCALC END