/* Copyright (C) 1986-2000 Simon N. Wood snw@st-and.ac.uk This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. (www.gnu.org/copyleft/gpl.html) You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/ /* various spline routines */ #include #include #include #include "matrix.h" #include "spline.h" #define SQR(a) ((a)*(a)) #define CUBE(a) ((a)*(a)*(a)) #define QUAD(a) ((a)*(a)*(a)*(a)) /* The next 4 functions are basis functions for the 1st derivative representation of a cubic spline */ void ErrorMessage(char *msg,int fatal); double b0(x0,x1,x) double x0,x1,x; /* multiplies function value at x0 */ { double res,h,xx1; h=x1-x0;xx1=x-x1; res=2.0*(x-x0+0.5*h)*xx1*xx1/(h*h*h); return(res); } double b1(x0,x1,x) double x0,x1,x; /* multiplies function value at x1 */ { double res,h,xx0; h=x1-x0;xx0=x-x0; res= -2.0*(x-x1-0.5*h)*xx0*xx0/(h*h*h); return(res); } double d0(x0,x1,x) double x0,x1,x; /* multiplies gradient at x0 */ { double res,h,xx1; h=x1-x0;xx1=x-x1; res=(x-x0)*xx1*xx1/(h*h); return(res); } double d1(x0,x1,x) double x0,x1,x; /* multiplies gradient at x1 */ { double res,h,xx0; h=x1-x0;xx0=x-x0; res=xx0*xx0*(x-x1)/(h*h); return(res); } /* The next 4 functions are derivatives of the spline basis functions used above. */ double db0(x0,x1,x) double x0,x1,x; { double res,h,xx1; h=x1-x0;xx1=x-x1; res=2.0*(2.0*(x-x0+0.5*h)*xx1+xx1*xx1)/(h*h*h); return(res); } double db1(x0,x1,x) double x0,x1,x; { double res,h,xx0; h=x1-x0;xx0=x-x0; res= -2.0*(2.0*(x-x1-0.5*h)*xx0+xx0*xx0)/(h*h*h); return(res); } double dd0(x0,x1,x) double x0,x1,x; { double res,h,xx1; h=x1-x0;xx1=x-x1; res=(xx1*xx1+2.0*(x-x0)*xx1)/(h*h); return(res); } double dd1(x0,x1,x) double x0,x1,x; { double res,h,xx0; h=x1-x0;xx0=x-x0; res=(xx0*xx0+2.0*xx0*(x-x1))/(h*h); return(res); } /* the next 4 functions are the integrals of the basis functions given above from a to b */ double intb0(x0,x1,a,b) double x0,x1,a,b; { double h,r; h=x1-x0; r=2.0*((QUAD(b)-QUAD(a))/4.0 +(CUBE(b)-CUBE(a))*(h/2.0-2.0*x1-x0)/3.0 +(SQR(b)-SQR(a))*(SQR(x1)+2.0*x1*x0-h*x1)/2.0 +(b-a)*(h*SQR(x1)/2.0-SQR(x1)*x0))/CUBE(h); return(r); } double intb1(x0,x1,a,b) double x0,x1,a,b; { double h,r; h=x1-x0; r= -2*((QUAD(b)-QUAD(a))/4.0- (CUBE(b)-CUBE(a))*(h/2.0+2.0*x0+x1)/3.0 +(SQR(b)-SQR(a))*(SQR(x0)+2.0*x1*x0+h*x0)/2.0 -(b-a)*(h*SQR(x0)/2.0+SQR(x0)*x1))/CUBE(h); return(r); } double intd0(x0,x1,a,b) double x0,x1,a,b; { double h,r; h=x1-x0; r=((QUAD(b)-QUAD(a))/4.0-(CUBE(b)-CUBE(a))*(2.0*x1+x0)/3.0+ (SQR(b)-SQR(a))*(SQR(x1)+2.0*x0*x1)/2.0-(b-a)*x0*SQR(x1))/SQR(h); return(r); } double intd1(x0,x1,a,b) double x0,x1,a,b; { double h,r; h=x1-x0; r=((QUAD(b)-QUAD(a))/4.0-(CUBE(b)-CUBE(a))*(2.0*x0+x1)/3.0+ (SQR(b)-SQR(a))*(SQR(x0)+2.0*x0*x1)/2.0-(b-a)*x1*SQR(x0))/SQR(h); return(r); } matrix getD(h,nak) matrix h;int nak; /* the matrix mapping the value of the spline to the gradients at the knots. nak is true for 'not-a-knot' end conditions at the early end, otherwise 'natural' end conditions are used. If there are only 2 knots then the spline is taken as a straight line if only 1 a constant. */ { long i,j,n; matrix T,D,Res; n=h.r+1; T=initmat(n,n);D=initmat(n,n);Res=initmat(n,n); for (i=0;i=k[i]&&x<=k[i+1])) return(1.0); if (i>n-2||i<0) return(0.0); if (x=k[i+1]) return(0.0); else return(1.0); } else { j1=i+m;if (j1>n-1) j1=n-1;j0=i;if (j0<0) j0=0; if (j0>=j1) z0=0.0;else z0=(x-k[j0])/(k[j1]-k[j0]); j1=i+m+1;if (j1>n-1) j1=n-1;j0=i+1;if (j0<0) j0=0; if (j0>=j1) z1=0.0;else z1=(k[j1]-x)/(k[j1]-k[j0]); return(z0*bsbf(m-1,k,x,i,n)+z1*bsbf(m-1,k,x,i+1,n)); } } int bsmap(double *b,double *k,double x,int n) /* given a knot sequence k_0 ..... k_{n-1} this routine returns the 4 non-zero cubic b splines basis functions at x - these are returned in b. The routine also returns the index of the last basis function in the series of 4..... so s(x) = b[0]*p_{i-3} + b[1]*p_{i-2} + b[2]*p_{i-1} + b[3]*p_i where p is the parameter vector, which has 3 more coefficients than there are knots. This routine is not suitable for extrapolation beyond [k_0,k_{n-1}]. */ { static ik=0L; int i; /* locate the knot interval */ if (xk[n-1]) ErrorMessage("You are trying to extrapolate with bsmap()",1); while (k[ik]>x) ik--; while (ikt.M[ia+1][0])&&(iat.M[ib+1][0])&&(ibt.V[i+1])&&(iM[i*nop+k][j]=xm.V[j]; } } tmap(xm,xmg,x,x.V[x.r-1],0); for (j=0;jM[A->r-1][j]=xm.V[j]; } else { dt=(x.V[x.r-1]-x.V[0])/(nop-1); for (i=0;iM[i][j]=xm.V[j]; } } tmap(xm,xmg,x,x.V[0],1); /* resetting tmap routine */ (*b)=initmat(totn,1L); freemat(xm);freemat(xmg); } void getHBH(HBH,h,nak,rescale) matrix *HBH,h;int nak,rescale; /* Generates the wiggliness measure matrix for vector h; nak=0 for natural end conditions or nak=1 to use the not a knot condition at the lower end; set rescale=1 to produce a measure rescaled for the unit interval, set to zero otherwise */ { long n,i,j; matrix C,B,BI,H,hn; double interval=0.0; n=h.r; if (rescale) { for (i=0;i=b ensuring monotonic change of the cubic spline interpolating (x ,y ) where h = x -x i i i i+1 i D is the matrix mapping y to gradients at x, which must be supplied to the routine along with h. Up should be non-zero for increase, zero for decrease. pos should be non-zero for a non-negative spline. */ { long i,j,n; double m; n=h.r; if (up) m= -1.0; else m=1.0; if (pos) (*A)=initmat(4*n+1,n+1); else (*A)=initmat(4*n,n+1); for (i=0;iM[i][j]=(D.M[i][j]+3.0/h.V[i])*m; /**not certain of d.M update**/ A->M[i+n][j]=(D.M[i+1][j]+3.0/h.V[i])*m; A->M[i+2*n][j]=m; A->M[i+3*n][j]= -D.M[i][j]*m; } else if (j==(i+1)) { A->M[i][j]=(D.M[i][j]-3.0/h.V[i])*m; A->M[i+n][j]=(D.M[i+1][j]-3.0/h.V[i])*m; A->M[i+2*n][j]= -m; A->M[i+3*n][j]= -D.M[i][j]*m; } else { A->M[i][j]=D.M[i][j]*m; A->M[i+n][j]=D.M[i+1][j]*m; A->M[i+2*n][j]=0.0; A->M[i+3*n][j]= -D.M[i][j]*m; } } } if (pos) { for (j=0;jM[4*n][j]=0.0; if (up) A->M[4*n][0]=1.0; else A->M[4*n][n]=1.0; } } double spline(x,p,g,z) matrix x,p,g; double z; /* evaluates the spline with knots at the positions in x, values at the knots in p and gradients at the knots in g at position z */ { static int i=0; /* x.M[i]<=zz)&&(i>0)) i--; while ((xV[i+1]<=z)&&(iz)&&(i>0)) i--; while ((xV[i+1]<=z)&&(i0.0)) sig=S[i+1]; else sig=bV[i]; if (sig>=0.0) bV[i]=min(max(0.0,bV[i]),3.0*min(fabs(S[i]),fabs(S[i+1]))); else bV[i]=max(min(0.0,bV[i]),-3.0*min(fabs(S[i]),fabs(S[i+1]))); } #ifdef SUNC cfree(S); #else free(S); #endif } void grad_limit(x,y,g,lim) matrix x,y,g;double lim; /* This routine was designed for use with the 'mort.c' program and attempts to make sure that the gradient of a piecewise polynomial is always less than or equal to the value given by 'lim'. Algorithm is quite cunning and should always work. 1/97 */ { long i; /* first subtract x*lim from the data and lim from the gradients - then Hyman filter the results for co-monotonicity, before adding the corrections back on again */ for (i=0;iy.V[i]) { ErrorMessage("Faulty maturation at age data",1);} } /* now apply Hyman filter */ hyman_filter(x,y,g); /* ... and add on line previously subtracted */ for (i=0;i=0;i--) { c.V[i]=z.V[i]-mu.V[i]*c.V[i+1]; b.V[i]=(a.V[i+1]-a.V[i])/h.V[i]- h.V[i]*(c.V[i+1]+2.0*c.V[i])/3.0; d.V[i]=(c.V[i+1]-c.V[i])/(3.0*h.V[i]); } freemat(z);freemat(mu);freemat(al);freemat(h);freemat(l); } */ void splcoeffs(x,a,b,c,d) matrix x,a,b,c,d; /* returns coefficients for 2 3 s(x)=a + b (x-x ) + c (x-x ) + d (x-x ) . i i i i i i i given the vectors x and a. Assumes that a spline with 1 knot is a constant and that 2 knots implies a straight line. This version allocates no extra memory on the heap. */ { long i,n; double h0,h1,li,*xV,*aV,*bV,*cV,*dV; n=x.r; xV=x.V;aV=a.V;bV=b.V;cV=c.V;dV=d.V; if (n==1L) { bV[0]=cV[0]=dV[0]=0.0;return ;} if (n==2L) { bV[1]=bV[0]=(aV[1]-aV[0])/(xV[1]-xV[0]);cV[0]=dV[0]=0.0;return ;} h1=xV[1]-xV[0]; for (i=1;i=0;i--) { h1=xV[i+1]-xV[i]; cV[i]=bV[i]-dV[i]*cV[i+1]; bV[i]=(aV[i+1]-aV[i])/h1- h1*(cV[i+1]+2.0*cV[i])/3.0; dV[i]=(cV[i+1]-cV[i])/(3.0*h1); } } double spl(no,x,y,x0,x1,mode,resetx,resety) int no,mode,resetx,resety;matrix x,y;double x0,x1; /* this is a routine that stores a number of spline functions for evaluation no is the index number for the splines coefficients. x and y are the x-y values interpolated, the x & y values need only be supplied when resetx/y is non-zero, they are stored between times; mode determines what sort of evaluation. 0 for integration from x0 to x1. 1 for evaluation at x0 2 for the derivative at x0. resetx/y is set to 1 to reset the coefficients for spline 'no'. To clear space resetx=1 resety=1 and x.r= 0. */ { static matrix a[125],b[125],c[125],d[125],xn[125]; static int on[125]; static long ipos[125],jpos[125]; long i; double res,xp; if ((no>124)||(no<0)) { ErrorMessage("Silly index passed to spl.",1);} if ((resetx)||(resety)) { if (((resetx)&&(y.r!=x.r))||((!resetx)&&(y.r!=xn[no].r))) { ErrorMessage("x and y vectors passed to spl() are not the same length.",1); } if (on[no]) { if (resetx) freemat(xn[no]); freemat(b[no]);freemat(c[no]);freemat(d[no]); if (resety) freemat(a[no]); } if (x.r==0 && resetx==1) { on[no]=0; return(0.0);} if (resetx) xn[no]=initmat(x.r,1L);b[no]=initmat(y.r,1L); c[no]=initmat(b[no].r,1L);d[no]=initmat(b[no].r,1L); if (resety) a[no]=initmat(y.r,1L); if (resetx) for (i=0;ixn[no].V[xn[no].r-1])) ErrorMessage("x0 out of bound in spl()."); if ((!mode)&&((x1xn[no].V[xn[no].r-1]))) ErrorMessage("x1 out of bound in spl().");*/ x0=max(x0,xn[no].V[0]);x0=min(x0,xn[no].V[xn[no].r-1]); x1=max(x1,xn[no].V[0]);x1=min(x1,xn[no].V[xn[no].r-1]); while ((xn[no].V[ipos[no]]>x0)&&(ipos[no]>0)) ipos[no]--; while ((xn[no].V[ipos[no]+1]x1)&&(jpos[no]>0)) jpos[no]--; while ((xn[no].V[jpos[no]+1]=1;i--) { QTz(i,i+2,V[i].Ri[2][1],V[i].Ri[2][0],Wy); QTz(i,i+1,V[i].Ri[1][1],V[i].Ri[1][0],Wy); QTz(i,n+i,U[i].Rni[0][1],U[i].Rni[0][0],Wy); if (i!=(n-2)) QTz(i+1,n+i,U[i].Rni[1][1],U[i].Rni[1][0],Wy); } residual=0.0; for (i=1;i<=n;i++) residual+=Wy[i]*Wy[i]; /** Calculate the Trace of the influence matrix **/ L[3][1]=V[n-2].Ri[2][1];L[3][3]=-V[n-2].Ri[2][0]; L[3][2]=-L[3][1]*V[n-2].Ri[1][0];L[3][1]=L[3][1]*V[n-2].Ri[1][1]; X[3]=-L[3][1]*U[n-2].Rni[0][0]; L[3][1]=L[3][1]*U[n-2].Rni[0][1]; TrA[n]=L[3][3]*L[3][3]; L[3][3]=L[3][2];L[3][2]=L[3][1]; L[2][1]=V[n-3].Ri[2][1];L[2][3]=-V[n-3].Ri[2][0]; L[3][1]=L[3][3]*V[n-3].Ri[2][0]; L[3][3]=L[3][3]*V[n-3].Ri[2][1]; L[2][2]=-L[2][1]*V[n-3].Ri[1][0]; L[2][1]=L[2][1]*V[n-3].Ri[1][1]; Lt=L[3][1]*V[n-3].Ri[1][1]+L[3][2]*V[n-3].Ri[1][0]; L[3][2]=L[3][2]*V[n-3].Ri[1][1]-L[3][1]*V[n-3].Ri[1][0]; L[3][1]=Lt; X[2]=-L[2][1]*U[n-3].Rni[0][0];L[2][1]=L[2][1]*U[n-3].Rni[0][1]; X[3]=-L[3][1]*U[n-3].Rni[0][0];L[3][1]=L[3][1]*U[n-3].Rni[0][1]; L[2][2]=L[2][2]*U[n-3].Rni[1][1]+X[2]*U[n-3].Rni[1][0]; L[3][2]=L[3][2]*U[n-3].Rni[1][1]+X[3]*U[n-3].Rni[1][0]; TrA[n-1]=(L[3][3]*L[3][3]+L[2][3]*L[2][3]); givens(L[2][1],L[3][1],&c,&s); /** The succesive rotation **/ L[2][1]=L[2][1]*c+L[3][1]*s; Lt=L[2][2]*c+L[3][2]*s; L[3][2]=L[3][2]*c-L[2][2]*s;L[2][2]=Lt; L[3][3]=L[3][2];L[2][3]=L[2][2];L[2][2]=L[2][1]; for (i=(n-4);i>=1;i--) { L[1][3]=-V[i].Ri[2][0];L[1][1]=V[i].Ri[2][1]; L[2][1]=L[2][3]*V[i].Ri[2][0];L[2][3]=L[2][3]*V[i].Ri[2][1]; L[3][1]=L[3][3]*V[i].Ri[2][0];L[3][3]=L[3][3]*V[i].Ri[2][1]; givens(L[1][1],L[3][1],&c,&s);s=-s; /** Rotation to remove upper element BEFORE it propagates **/ L[1][1]=L[1][1]*c-L[3][1]*s; L[1][2]=-L[1][1]*V[i].Ri[1][0]; L[1][1]=L[1][1]*V[i].Ri[1][1]; Lt=L[2][1]*V[i].Ri[1][1]+L[2][2]*V[i].Ri[1][0]; L[2][2]=L[2][2]*V[i].Ri[1][1]-L[2][1]*V[i].Ri[1][0]; L[2][1]=Lt; for (k=1;k<=2;k++) { X[k]=-L[k][1]*U[i].Rni[0][0]; L[k][1]=L[k][1]*U[i].Rni[0][1]; L[k][2]=L[k][2]*U[i].Rni[1][1]+X[k]*U[i].Rni[1][0]; } givens(L[1][1],L[2][1],&c,&s); /** Second rotation removing upper element **/ L[1][1]=L[1][1]*c+L[2][1]*s; Lt=L[1][2]*c+L[2][2]*s; L[2][2]=L[2][2]*c-L[1][2]*s;L[1][2]=Lt; TrA[i+2]=L[3][3]*L[3][3]+L[2][3]*L[2][3]+L[1][3]*L[1][3]; if (i!=1) { L[3][3]=L[2][2];L[2][3]=L[1][2];L[2][2]=L[1][1]; } } TrA[2]=L[2][2]*L[2][2]+L[1][2]*L[1][2]; TrA[1]=L[1][1]*L[1][1]; trace=0.0; for (i=1;i<=n;i++) trace+=TrA[i]; for (i=1;i<=n;i++) ci[i]=1.96*sqrt(residual/trace)*sqrt(1.0-TrA[i]); if (method==0) { residual=0.0; for (i=1;i<=n;i++) residual+=Wy[i]*Wy[i]/(TrA[i]*TrA[i]); return(residual/n); /* ordinary cross validation */ } else if (method==1) return(residual+2*sig2*(n-trace)); /* expected loss function */ else return(n*residual/(trace*trace)); /* generalized cross validation */ } /****************************************************************************/ /** The following routine performs a global search for the minimiser of **/ /** GCV, OCV or optimal loss score starting at low p **/ /** i.e. very smooth this is necessary because at very high p with very **/ /** noisy data the gcv function seems to tend to zero. Once the approximate**/ /** minimum is found a bisection search is performed, for the best p. **/ /** Failure of the algorithm to converge should be countered in the first **/ /** instance by lowering the starting value of p. **/ /****************************************************************************/ double ss_minimiser(ub,lb,U,V,TrA,Wy,h,ci,w,x,y,sig2,n,method) double **ub,**lb,*TrA,*Wy,*h,*ci,*w,*x,*y,sig2; long n;int method;Ru *U;Rv *V; { double p0,p1,p2,cv1,cv2=0.0,max=0.0,pt,p1t,ft,f1t,tau; long i; char down=0; p1=1e-7; for (i=0;i<82L;i++) { cv1=GCV(p1,ub,lb,U,V,TrA,Wy,h,ci,w,x,y,sig2,n,method); if (cv1>max) max=cv1; if (cv1cv2*1.01)&&(p1>20*p2)) i=82L; p1*=1.4; } /* golden section search to polish minimisation */ p0=p2/1.4;p1=p2*1.4; tau=2.0/(1.0+sqrt(5.0)); pt=p0+(p1-p0)*tau; ft=GCV(pt,ub,lb,U,V,TrA,Wy,h,ci,w,x,y,sig2,n,method); p1t=p0+(p1-p0)*(1.0-tau); f1t=GCV(p1t,ub,lb,U,V,TrA,Wy,h,ci,w,x,y,sig2,n,method); while ((pt-p1t)>1e-5*fabs(pt+p1t)) { if (ft=1;i--) c[i+1]=(z[i]-lb[i][2]*c[i+2])/lb[i][1]; for (i=1;i<=n-1;i++) { d[i]=(c[i+1]-c[i])/(3*h[i]); b[i]=(a[i+1]-a[i])/h[i]-c[i]*h[i]-d[i]*h[i]*h[i]; } free(GTA);free(z); } /****************************************************************************/ /** The upper and lower bands ,ub and lb of the matrix C of H&dH (3.2) are**/ /** set up the lower band coming from the Choleski decomposition of H (3.1)**/ /****************************************************************************/ void ss_setup(ub,lb,x,h,w,n) double **ub,**lb, *x,*h,*w;long n; { double *hh,*hh1; long i; hh=(double *)calloc((size_t)n+1,sizeof(double)); hh1=(double *)calloc((size_t)n+1,sizeof(double)); if (!hh1) { ErrorMessage("Out of memory in ss_setup. Exiting .... ",1);} for (i=1;i<=n-1;i++) h[i]=x[i+1]-x[i]; for (i=1;i<=n-2;i++) hh[i]=2.0*(h[i]+h[i+1])/3.0; for (i=1;i<=n-3;i++) hh1[i]=h[i+1]/3.0; lb[1][1]=sqrt(hh[1]);lb[1][2]=hh1[1]/lb[1][1]; for (i=2;i<=n-3;i++) { lb[i][1]= sqrt(hh[i]-lb[i-1][2]*lb[i-1][2]); lb[i][2]= hh1[i]/lb[i][1]; } lb[n-2][1]=sqrt(hh[n-2]-lb[n-3][2]*lb[n-3][2]); for (i=1;i<=n-2;i++) { ub[i][1]=w[i]/h[i]; ub[i][2]=-w[i+1]*(1/h[i]+1/h[i+1]); ub[i][3]=w[i+2]/h[i+1]; } free(hh);free(hh1); } /****************************************************************************/ /** The spline function. **/ /****************************************************************************/ double polyspl(xx,a,b,c,d,x,i) double xx,*a,*b,*c,*d,*x;long i; { double xxi; xxi=xx-x[i]; return(a[i]+b[i]*xxi+c[i]*xxi*xxi+d[i]*xxi*xxi*xxi); } double fitss_array(lam,n,x,y,w,a,b,c,d,ci,method) long n; double lam,*x,*y,*w,**a,**b,**c,**d,**ci;int method; /* fits cubic smoothing spline to data in x,y using (relative) variance estimates in w. Note that this was translated from pascal code - and all arrays start from 1 as a result. a,b,c,d and ci are initialised in this routine. */ { double **ub,**lb; Ru *U; Rv *V; double *TrA,*Wy,*h,sig2=0.0; long i; for (i=1;i<=n;i++) sig2+=w[i];sig2/=n; for (i=1;i<=n;i++) w[i]=sqrt(w[i]/sig2); /* algorithm uses strange convention that if the model is Yi=f(xi)+ei where ei is an error term then E(ei*ei)=w[i]*w[i]*var where var is the error variance */ ss_setupstore(n,&ub,&lb,&U,&V,a,b,c,d,&TrA,&Wy,&h,ci); ss_setup(ub,lb,x,h,w,n); if (method==3) { GCV(lam,ub,lb,U,V,TrA,Wy,h,*ci,w,x,y,sig2,n,1);} else lam=ss_minimiser(ub,lb,U,V,TrA,Wy,h,*ci,w,x,y,sig2,n,method); ss_setup(ub,lb,x,h,w,n); /* needed to get back original lb for .... */ ss_coeffs(lb,*a,*b,*c,*d,h,y,w,Wy,n); for (i=1;i<=n;i++) w[i]=w[i]*w[i]*sig2; /* restoring w to original values */ ss_freestore(n,&ub,&lb,&U,&V,&TrA,&Wy,&h); return(lam); } double ss_fit(x,y,w,a,b,c,d,method,justa) matrix x,y,w,a,b,c,d;int method,justa; /* Routine for calculating cubic smoothing splines by Hutchinson & deHoog's method. This is really an interface routine to fitss_array, which works with arrays rather than matrices. Set justa=1 if only the fitted values are required, otherwise set justa=0. For method: 0 - OCV 1 - Optimal loss function (w must contain absolute variances) 2 - GCV 3 - use s.p. in a.V[0] All matrices must be initialised prior to calling (except b,c,d if justa=1) returns smoothing parameter. */ { double *ar,*br,*cr,*dr,*ci,lam; long i; x.V--;y.V--;w.V--; /* array version requires arrays starting at 1 */ lam=a.V[0]; // only used if method==3 lam =fitss_array(lam,x.r,x.V,y.V,w.V,&ar,&br,&cr,&dr,&ci,method); x.V++;y.V++;w.V++; /* resetting addresses to correct values */ if (justa) for (i=0;i