/* Routines for gcv for single and multiple smoothing parameter problems. Copyright (C) 1996-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. */ #define ANSI #include #include #include #include "matrix.h" #include "gcv.h" /* Routines based on tridiagonalisation of a transformed smoothness matrix See Gu, as reported in Wahba 1990 and Wood 2000. These routines are better than the svd routines, furthermore multiple smoothing parameters are dealt with. */ double TrInf(matrix *X,matrix *Z,matrix *w,matrix *S,double rho) /* Finds the trace of the influence matrix: rho*XZ[Z'X'WXZ*rho + Z'SZ]^{-1}Z'X'W Z is the column basis for a null space of constraints. It is assumed that these are stored efficiently as householder transformations in the manner of routine QT. For example to impose Cp=0: QT(Z,C,0);Z.r=C.r; written and tested 26/11/97 */ { double *rw,zz; matrix R,Q,U,T,l0,l1; long n,i,j,k; n=X->r; /* Get Q'R=W^{0.5} X Z */ rw=(double *)calloc((size_t)n,sizeof(double)); for (i=0;iV[i]); /* rw contains l.d. of W^{0.5} */ if (Z->r) { R=initmat(n,Z->c); mcopy (X,&R);HQmult(R,*Z,0,0);R.c-=Z->r; /* R=XZ */ } else { R=initmat(n,X->c);mcopy(X,&R);} /* case when Z=I */ for (i=0;i=nz*/ freemat(Q); R.r=R.c; /* getting rid of zero part */ InvertTriangular(&R); /* R now contains L */ T=initmat(S->r,S->c); mcopy(S,&T); if (Z->r) { HQmult(T,*Z,1,1);HQmult(T,*Z,0,0); T.r=T.c=Z->c-Z->r; } U=initmat(T.r,T.c); multi(3,U,R,T,R,1,0,0); for (j=T.c-1;j>=0;j--) for (i=0;i=0;i--) for (j=0;j<=i;j++) { zz=0.0; for (k=0;k<=i;k++) zz+=R.M[k][i]*T.M[k][j];T.M[i][j]=zz;} for (i=T.r-1;i>=0;i--) for (j=0;j<=T.c;j++) T.M[j][i]=T.M[i][j]; zz=0.0; for (i=0;i0.0) ubre=1; for (i=0;ir;i++) T->M[i][i] += r; /* forming I*r + T */ tricholeski(T,l0,l1); /* get LL'=I*r + T */ tr=1.0-triTrInvLL(l0,l1)*r/n; z->r=x->r; bicholeskisolve(x,z,l0,l1); for (i=0;ir;i++) { el=z->V[i]- r * x->V[i];rss+=el*el;T->M[i][i] -= r; } rss+=nx; if (!ubre) *sig2=rss/(n*tr); /* variance estimate - GCV only */ z->r=n;rss/=n; if (ubre) /* then use UBRE */ { el = rss - 2*(*sig2)*tr+ *sig2;tr=tr*tr;} else /* use GCV */ { tr=tr*tr;el=rss/tr;} *ress=rss;*trace=tr; /* debugging */ return(el)/*(n-tr*n-5)*(n-tr*n-5))*/; } double EasySmooth(matrix *T,matrix *z,double *v,double *df,long n,double *sig2) /* routine that minimises (||(I-r*(I*r + T)^{-1})z ||^2+||x||^2) / (n - r*Tr(I*r+T)^{-1})^2 This is a gcv score for a problem with an influence matrix: r JZ[Z'J'WJZ r + Z'SZ]^{-1}Z'J'W This will have been re-written using the decomposition: W^{0.5}JZ=QR as.... r W^{-0.5}QR(R'R r + Z'SZ)^{-1} R'Q'W^{0.5} which becomes.... r W^{-0.5}(I*r+L'Z'SZL)^{-1}W^{0.5} where L=R^{-1} The decomposition UTU' = L'Z'SZL is then formed. As is z=UQ'W^{0.5} y. Alternatively the routine minimises an equivalent UBRE score if *sig2>0.0 on entry. returns smoothing parameter and minimum gcv/ubre score. */ { matrix l0,l1,x; double r,V,rm,tr,maxr,minr,minV,rss,r0,r1,rt,r1t,ft,f1t,tau,nx; long k,mesh=50L,i; int gcv=1; minV=0.0; if (*sig2>0.0) gcv=0; /* Initialise work space matrices */ l0=initmat(T->r,1L);l1=initmat(T->r-1,1L);x=initmat(l0.r,1L); /* get initial smooth estimates */ tr=0.0;for (i=0;ir;i++) tr+=T->M[i][i];tr/=T->r; minr=tr*0.0000001;maxr=tr*100000.0;rm=exp(log(maxr/minr)/mesh); r=minr/rm; nx=0.0;for (i=x.r;iV[i]*z->V[i]; /* ||x||^2 */ for (k=0;k1e-5*fabs(rt+r1t)) { if (ft0.0 on entry then UBRE is used (which assumes that the variance sig2 is known), otherwise GCV is used. */ { double *rw,v,rho,zz; matrix Wy,R,Q,U,T,z,l0,l1; long n,i,j,k; n=y->r; /* Get Q'R=W^{0.5} X Z */ rw=(double *)calloc((size_t)n,sizeof(double)); for (i=0;iV[i]); /* rw contains l.d. of W^{0.5} */ Wy=initmat(n,1L); for (i=0;iV[i]; if (Z->r) { R=initmat(n,Z->c); mcopy (X,&R);HQmult(R,*Z,0,0);R.c-=Z->r; /* R=XZ */ /*matmult(R,*X,*Z,0,0); FZ *//* R=XZ - up to O(n^3) */ } else { R=initmat(n,X->c);mcopy(X,&R);} /* case when Z=I */ for (i=0;i=nz*/ R.r=R.c; /* getting rid of zero part */ InvertTriangular(&R); /* R now contains L */ /* following code is inefficient and should be optimized - DONE 26/11/97 and tested (quickly with matest)*/ T=initmat(R.c,R.c); U=initmat(S->r,S->r);mcopy(S,&U); if (Z->r) { HQmult(U,*Z,1,1);HQmult(U,*Z,0,0); U.r=U.c=Z->c-Z->r; } //multi(3,T,R,U,R,1,0,0); for (j=U.c-1;j>=0;j--) for (i=0;i=0;i--) for (j=0;j<=i;j++) { zz=0.0; for (k=0;k<=i;k++) zz+=R.M[k][i]*U.M[k][j];U.M[i][j]=zz;} for (i=U.r-1;i>=0;i--) for (j=0;j<=U.c;j++) U.M[j][i]=U.M[i][j]; mcopy(&U,&T); freemat(U); U=initmat(R.c,R.c); UTU(&T,&U); z=initmat(n,1L); for (i=0;iV[i]; /* z=W^{1/2}y */ OrthoMult(&Q,&z,0,Q.r,0,1,1); /* z=QW^{1/2}y */ z.r=T.r; /* z=[I,0]QW^{1/2}y */ OrthoMult(&U,&z,1,T.r-2,1,1,0); /* z=U'[I,0]QW^{1/2}y */ z.r=n; rho=EasySmooth(&T,&z,&v,df,n,sig2); l0=initmat(T.r,1L);l1=initmat(T.r-1,1L); for (i=0;ir=z.r; bicholeskisolve(y,&z,&l0,&l1); /* y=(I*rho+T)^{-1} z */ OrthoMult(&U,y,1,T.r-2,0,1,0); /* y=U(I*rho+T)^{-1} z */ p->r=R.r; matmult(*p,R,*y,0,0); if (Z->r) { p->r=Z->c; for (i=R.r;ic;i++) p->V[i]=0.0; HQmult(*p,*Z,1,0); /**p=vecmult(*Z,*p,0); FZ */ } for (i=0;ir;i++) p->V[i]*=rho; /* p=rho*ZLU(I+rho+T)^{-1} z */ y->r=n;for (i=T.r;ir;i++) y->V[i]=0.0; OrthoMult(&Q,y,0,Q.r,1,1,1); /* smoothed values now in y */ for (i=0;iV[i]*=rho/rw[i]; freemat(Q);freemat(U);freemat(R);freemat(T);freemat(z);freemat(Wy); freemat(l0);freemat(l1);free(rw); return(rho); } /* tediouscv and boringHg are debugging routines for MultiSmooth() */ double tediouscv(matrix R,matrix Q,matrix *LZSZL,matrix *y,double *rw, double *trial,double rho,int m,double *tr,double *rss,double sig2) { long i,j,l,n; matrix T,U,z,l0,l1,x; double v,nx; n=y->r; T=initmat(LZSZL[0].r,LZSZL[0].r); U=initmat(T.r,T.r); z=initmat(n,1L); for (i=0;iV[i]; /* z=W^{1/2}y */ OrthoMult(&Q,&z,0,Q.r,0,1,1); /* z=QW^{1/2}y */ nx=0.0;for (i=R.r;i= to null space dimension (parameter vector length, or columns of Z) - I'm not sure if this restriction is fundamental. If any of the m elements of theta[] are -ve then automatic initialisation of smoothing parameters takes place. Otherwise the supplied theta[i]s are used as initial estimates. On exit theta[i] contains best estimates of theta[i]/rho. NOTE that for some problems it may make sense to initialise by running first with very low s.p.s and then using the Wahba suggested 2nd guesses - not implemented yet. Z is actually supplied as a matrix containing a series of householder transformations, as produced by QT when fullQ==0... this improves efficiency considerably.... To obtain Z for Cp=0: call QT(Q,C,0). Then set Q.r=C.r; Q is now used as Z. Supplying *sig2 as a positive number signals the routine to use UBRE, rather than GCV, since *sig2 is known. Supplying *sig2 as zero or negative causes the routine to use GCV. (added 15/1/99) */ { double *rw,tr,*eta,*del,*trial,trA,a,b,*trdA,**trd2A,*db,**d2b,*da,**d2a, rho,v,vmin=0.0,x,**RM,*pp,**MM,**M1M,**M2M,*pp1,tdf,xx1,xx2,tol, *pinf,*ninf; long i,j,k,l,n,np,nz; int iter,reject,ok=1,autoinit=0,op=0,trials,ubre=0; matrix z,l0,l1,Q,R,C,ZC,*LZrS,*LZSZL,T,U,A,Wy,Hess,g,c,*H,*ULZSZLU, *ULZrS,*dAy,*dpAy,d,Ay; if (*sig2>0.0) ubre=1; /* use UBRE rather than GCV */ n=y->r; /* number of datapoints */ np=J->c; /* number of parameters */ for (i=0;ir) nz=np-Z->r;else nz=np; /* dimension of null space */ A=initmat(np,np); /* workspace matrix */ c=initmat(n,1L); /* " vector */ d=initmat(n,1L); Ay=initmat(n,1L); Hess=initmat((long)m,(long)m);g=initmat((long)m,1L); /* set up storage */ trdA=(double *)calloc((size_t)m,sizeof(double)); da=(double *)calloc((size_t)m,sizeof(double)); db=(double *)calloc((size_t)m,sizeof(double)); trd2A=(double **)calloc((size_t)m,sizeof(double)); d2a=(double **)calloc((size_t)m,sizeof(double)); d2b=(double **)calloc((size_t)m,sizeof(double)); ninf=(double *)calloc((size_t)m,sizeof(double)); pinf=(double *)calloc((size_t)m,sizeof(double)); for (k=0;kV[i]); /* rw contains l.d. of W^{0.5} */ Wy=initmat(n,1L); for (i=0;iV[i]; if (Z->r) { R=initmat(n,np);mcopy(J,&R); HQmult(R,*Z,0,0);R.c=nz; /* R=JZ */ } else { R=initmat(n,np);mcopy(J,&R);} /* case when Z=I */ RM=R.M; for (i=0;i=nz (also inefficient to do at all)*/ R.r=R.c; /* getting rid of zero part */ InvertTriangular(&R); /* R now contains L - explicit R formation isn't very efficient */ /* Form the matrices L'Z'S^{0.5} and L'Z'S_iZL */ ZC=initmat(np,np); LZrS=(matrix *)calloc((size_t)m,sizeof(matrix)); LZSZL=(matrix *)calloc((size_t)m,sizeof(matrix)); ULZSZLU=(matrix *)calloc((size_t)m,sizeof(matrix)); H= (matrix *)calloc((size_t)m,sizeof(matrix)); ULZrS=(matrix *)calloc((size_t)m,sizeof(matrix)); for (l=0;lr) { ZC.c=C.c;ZC.r=np; /* set ZC = [0,C',0]' */ for (i=0;i1||!autoinit) /* check smoothing parameters won't lead to overflow */ for (i=0;ipinf[i]) trial[i]=pinf[i]; /* form S the combined smooth measure */ for (i=0;iV[i]; /* z=W^{1/2}y */ OrthoMult(&Q,&z,0,Q.r,0,1,1); /* z=QW^{1/2}y */ z.r=R.r; /* z=[I,0]QW^{1/2}y */ OrthoMult(&U,&z,1,T.r-2,1,1,0); /* z=U'[I,0]QW^{1/2}y */ z.r=n; /* z->[z,x_1]' */ if (!ubre) *sig2=-1.0; /* setting to signal GCV rather than ubre */ rho=EasySmooth(&T,&z,&v,&tdf,n,sig2); /* do a cheap minimisation in rho */ z.r=R.r; if (!iter||vtol*(1+v)) ok=1; xx1=0.0;for (i=0;ixx2) ok=1; xx1=0.0;for (i=0;ipow(tol,1.0/3)*(1+v)) ok=1; for (i=0;i3) ok=0; } if (op) printf("\n%12.6g %12.6g",v,vmin); } /* get choleski decomposition of (I*rho+T) */ for (i=0;iV[i]=c.V[i]; /* use p_z not Zp_z */ c.r=np; for (l=0;lV[j]*LZrS[l].M[j][i]; } tr=0.0;for (i=0;ir) { p->r=np;for (i=0;iV[i]=c.V[i]; for (i=nz;iV[i]=0.0; HQmult(*p,*Z,1,0); /* Q [c,0]'= [Z,Y][c,0]' = Zc */ } else { p->r=np;for (i=0;iV[i]=c.V[i];} for (l=0;lr; OrthoMult(&Q,dAy+l,0,Q.r,1,1,1); for (i=0;ir;for (i=T.r;ir;i++) Ay.V[i]=0.0; OrthoMult(&Q,&Ay,0,Q.r,1,1,1); for (i=0;ir; for (i=T.r;ir;i++) c.V[i]=0.0; // and premutiplying result by (0',I')' OrthoMult(&Q,&c,0,Q.r,1,1,1); // premultiplying by Q' for (i=0;i= to null space dimension (parameter vector length, or columns of Z) - I'm not sure if this restriction is fundamental. If any of the m elements of theta[] are -ve then automatic initialisation of smoothing parameters takes place. Otherwise the supplied theta[i]s are used as initial estimates. On exit theta[i] contains best estimates of theta[i]/rho. NOTE that for some problems it may make sense to initialise by setting running first with very low s.p.s and then using the Wahba suggested 2nd guesses - not implemented yet. Z is actually supplied as a matrix containing a series of householder transformations, as produced by QT when fullQ==0... this improves efficiency considerably.... old, To obtain Z for Cp=0: call QT(Q,C,0). Then set Q.r=C.r; Q is now used as Z. If *sig2>0.0 on entry then UBRE replaces GCV as the s.p. selection criterion. */ { double *rw,tr,*eta,*del,*trial,trA,a,b,*trdA,**trd2A,*db,**d2b,*da,**d2a, rho,v,vmin=0.0,x,**RM,*pp,**MM,**M1M,**M2M,*pp1,tdf,xx1,xx2,tol, *pinf,*ninf,*lam; long i,j,k,l,n,np,nz; int iter,reject,ok=1,autoinit=0,op=0,ubre=0; matrix z,l0,l1,Q,R,C,ZC,*LZrS,*LZSZL,T,U,A,Wy,Hess,g,c,*H,*ULZSZLU, *ULZrS,*dAy,*dpAy,d,Ay,Ht; if (*sig2>0.0) ubre=1; /* signals UBRE rather than GCV */ n=y->r; /* number of datapoints */ np=J->c; /* number of parameters */ for (i=0;imp if (Z->r) nz=np-Z->r;else nz=np; /* dimension of null space */ A=initmat(np,np); /* workspace matrix */ c=initmat(n,1L); /* " vector */ d=initmat(n,1L); Ay=initmat(n,1L); Hess=initmat((long)m,(long)m);g=initmat((long)m,1L); trdA=(double *)calloc((size_t)m,sizeof(double)); da=(double *)calloc((size_t)m,sizeof(double)); db=(double *)calloc((size_t)m,sizeof(double)); trd2A=(double **)calloc((size_t)m,sizeof(double)); d2a=(double **)calloc((size_t)m,sizeof(double)); d2b=(double **)calloc((size_t)m,sizeof(double)); ninf=(double *)calloc((size_t)m,sizeof(double)); pinf=(double *)calloc((size_t)m,sizeof(double)); for (k=0;kV[i]); /* rw contains l.d. of W^{0.5} */ Wy=initmat(n,1L); for (i=0;iV[i]; if (Z->r) { R=initmat(n,np);mcopy(J,&R); HQmult(R,*Z,0,0);R.c=nz; /* R=JZ */ } else { R=initmat(n,np);mcopy(J,&R);} /* case when Z=I */ RM=R.M; for (i=0;i=nz*/ R.r=R.c; /* getting rid of zero part */ InvertTriangular(&R); /* R now contains L */ /* Form the matrices L'Z'S^{0.5} and L'Z'S_iZL */ ZC=initmat(np,np); LZrS=(matrix *)calloc((size_t)m,sizeof(matrix)); LZSZL=(matrix *)calloc((size_t)m,sizeof(matrix)); ULZSZLU=(matrix *)calloc((size_t)m,sizeof(matrix)); H= (matrix *)calloc((size_t)m,sizeof(matrix)); ULZrS=(matrix *)calloc((size_t)m,sizeof(matrix)); for (l=0;lr) { ZC.c=C.c;ZC.r=np; /* set ZC = [0,C',0]' */ for (i=0;i1||!autoinit) /* check smoothing parameters won't lead to overflow */ for (i=0;ipinf[i]) eta[i]=pinf[i]; /* form S the combined smooth measure */ for (i=0;iV[i]; /* z=W^{1/2}y */ OrthoMult(&Q,&z,0,Q.r,0,1,1); /* z=QW^{1/2}y */ z.r=R.r; /* z=[I,0]QW^{1/2}y */ OrthoMult(&U,&z,1,T.r-2,1,1,0); /* z=U'[I,0]QW^{1/2}y */ z.r=n; /* z->[z,x_1]' */ if (!ubre) *sig2=-1.0; /* signalling use of GCV */ rho=EasySmooth(&T,&z,&v,&tdf,n,sig2); /* do a cheap minimisation in rho */ z.r=R.r; if (!iter||vtol*(1+v)) ok=1; xx1=0.0;for (i=0;ixx2) ok=1; xx1=0.0;for (i=0;ipow(tol,1.0/3)*(1+v)) ok=1; for (i=0;i3) ok=0; } if (op) printf("\n%12.6g %12.6g",v,vmin); } /* get choleski decomposition of (I*rho+T) */ for (i=0;iV[i]=c.V[i]; /* use p_z not Zp_z */ c.r=np; for (l=0;lV[j]*LZrS[l].M[j][i]; } tr=0.0;for (i=0;ir) { p->r=np;for (i=0;iV[i]=c.V[i]; for (i=nz;iV[i]=0.0; HQmult(*p,*Z,1,0); /* Q [c,0]'= [Z,Y][c,0]' = Zc */ } else { p->r=np;for (i=0;iV[i]=c.V[i];} for (l=0;lr; OrthoMult(&Q,dAy+l,0,Q.r,1,1,1); for (i=0;ir;for (i=T.r;ir;i++) Ay.V[i]=0.0; OrthoMult(&Q,&Ay,0,Q.r,1,1,1); for (i=0;ir; for (i=T.r;ir;i++) c.V[i]=0.0; OrthoMult(&Q,&c,0,Q.r,1,1,1); for (i=0;i