/* Copyright (C) 1991-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.*/ /* Routines for quadratic programming and other constrained optimization. */ #include #include #include #include #include "matrix.h" #include "qp.h" #include "gcv.h" qpoutdatatype qpoutdata; #define DELMAX 35L void ErrorMessage(char *msg,int fatal); #define round(a) ((a)-floor(a) <0.5 ? (int)floor(a):(int) floor(a)+1) matrix addconQT(Q,T,a,u) matrix *Q,T,a,*u; /* A constraint, a (a row vector), is added to the QT factorization of the working set. T must have been initialised square, and then had T.r set to correct length. */ { long q,i,j; double la,ra=0.0,*cV,*bV,*T1V; matrix b,c; c=initmat(Q->r,1L);b=initmat(Q->r,1L);(*u)=initmat(Q->r,1L); for (i=0;iM[j][i]; la=dot(c,c); cV=c.V;bV=b.V; q=T.c-T.r-1; if (q!=0L) { for (i=q+1;i0.0) bV[q]= -bV[q]; householder(u,c,b,q); Hmult((*Q),(*u)); } else for (i=0;ic-T->r-1 rows to store the Givens rotations and must be initialized outside the routine. */ { long q,i,j; double Qi,r,cc,ss,*bV,*sV,*cV,**QM,*QV,bb,bb1; matrix b; b.V=T->M[T->r]; b.r=Q->r;b.c=1L; for (i=0;ic;i++) b.V[i]=0.0; for (i=0;ir;j++) b.V[i]+=Q->M[j][i]*a->V[j]; /* now calculate a series of Givens rotations that will rotate the null basis so that it is orthogonal to new constraint a */ bV=b.V;cV=c->V;sV=s->V;QM=Q->M; q=T->c-T->r-1; /* number of Givens transformations needed */ for (i=0;ir;j++) { QV=QM[j]; Qi=QV[i]; QV[i]=cc*Qi + ss*QV[i+1]; QV[i+1]=ss*Qi - cc*QV[i+1]; } } T->r++; } matrix delconQT(Q,T,sth) matrix Q,T;long sth; /* the sth constraint is deleted from the QT factorization of the working */ /* set where s>=0. */ { long i,j,colj,coli,k,Tr,Tc,Qr,T1r,T1c; double r,s,c,xi,xj,**TM,**QM,**T1M,*TV,*QV,*T1V; Tr=T.r;TM=T.M;QM=Q.M;Tc=T.c;Qr=Q.r; for (i=sth+1;iM;aV=a.V; k=ZGZ->r; for (i=0;ic++;ZGZ->r++; } void deleteconstraint(ZGZ,gk,pk,R,G,cT,Q,Ac,I,T,y,s,tk,getg,newR) matrix *ZGZ,gk,pk,*R,G,cT,Q,*Ac,I,*T,y;long s,*tk;int getg,newR; /* The sth constraint is deleted from Ac, Q, T and tk are updated ZGZ, R, and pk are updated if newR is non-zero gk is obtained from getgk() if newR and getg are non-zero ZGZ is updated iff ZGZ->r!=0. */ { long n,i,j,Qr; matrix Atemp,z,a,b,pz; double tot,gamma,beta,**RM,*pzV,**QM,*zV,*aV,*pkV,*bV,**AcM,**AtempM,*IV; (*T)=delconQT(Q,(*T),s); n=Q.c-(*tk);(*tk)--; QM=Q.M;Qr=Q.r;pkV=pk.V; if ((getg)&&(!newR)) { ErrorMessage("Bad call to deleteconstraint().",1);} if (newR) { z=initmat(Q.r,1L);a=initmat(Q.r,1L);b=initmat(Q.r,1L);pz=initmat(n+1,1L); aV=a.V;bV=b.V;zV=z.V; for (i=0;ir) updateZGZ(ZGZ,a); (*R)=choleskiupdate((*R),a); freemat(a);freemat(b); } Atemp=(*Ac);(*Ac)=initmat((*Ac).r-1,(*Ac).c); AcM=Ac->M;AtempM=Atemp.M;IV=I.V; for (i=0;iM;pzV=pz.V; if (getg) getgk(gk,G,cT,y); gamma=dot(gk,z); beta= -gamma/RM[n][n]; for (i=n;i>=0;i--) { tot=0.0; for (j=i+1;j0.0/*reltol*/) /* the min. in the null space would violate cons.*/ { ay=0.0;apk=0.0; for (j=0;j0.0) { alpha=(b.V[i]-ay)/apk; if (alpha -1) /* This prevents rounding error induced reduction of the*/ zap.M[imin]=1.0; /* step length (alpha) to zero, in the next bit of code,*/ if (coninfile) fseek(in,2L*(long)sizeof(long),0); for (i=0;ireltol) { alphamin=0.0;imin=i; /* resets step if constraint violation */ } /* has crept in */ } } #endif for (i=0;iM!=NULL) freemat((*R));(*R)=initmat(n,n); Gc=G.c;ZGZM=ZGZ->M;QM=Q.M;GM=G.M; if (n!=0) { if (Greset) /* G has changed so Z'GZ must be completely reformed*/ { B=initmat(G.r,n);ZGZc=ZGZ->r=ZGZ->c=n;Bc=B.c; BM=B.M; for (i=0;ir;i++) for (j=i;jr--;ZGZ->c--; } choleski((*ZGZ),(*R),0,0); } else { ZGZ->r--;ZGZ->c--; } } void getAc(coninfile,confn,Ain,Af,Ac,I,tk) int coninfile;char *confn;matrix Ain,Af,*Ac,I;long tk; /* Given the matrix I indexing the active constraints of A, the active */ /* constraint matrix Ac is produced. */ { long i,j,k,r,c; matrix A; FILE *in; char *errs; if (coninfile) { in=fopen(confn,"rb"); if (in==NULL) { errs=(char *)calloc(200,sizeof(char)); sprintf(errs,"%s not found, nothing read ! ",confn);ErrorMessage(errs,1); } fread(&r,sizeof(long),1,in); fread(&c,sizeof(long),1,in); A=initmat(1L,c); } else { r=Ain.r;c=Ain.c;A.r=1L;A.c=c;A.vec=1;} if (Ac->M!=NULL) freemat((*Ac)); (*Ac)=initmat(tk,c); for (i=0;iM[i][j]=Af.M[i][j]; for (i=Af.r;iM[i][j]=A.V[j]; } if (coninfile) { fclose(in); freemat(A); } } void Rupdate(ZGZ,R,s,c) matrix *ZGZ,*R,s,c; /* Updates ZGZ (iff ZGZ->r!=0) and R using Givens rotations calculated in GivensAddconQT for updating the null space after constraint addition. */ { long i,j; double x0,x1,ss,cc,r,**RM,*RM0,*RM1,**ZGZM; /* Update the choleski factor R first */ RM=R->M; for (i=0;ir;j++) /* apply to rest of matrix */ { RM0=RM[j];x0=RM0[i];x1=RM0[i+1]; RM0[i]=cc*x0+ss*x1; RM0[i+1]=ss*x0 -cc*x1; } } R->r--;R->c--; /* now update ZGZ. There is an argument for not updating this matrix, since it isn't needed for the Q.P. It is used for cross validation - but only for full cross validation - not the approximate version, and it can be obtained in 0(n^3) ops from R anyway - only updated if ZGZ->r!=0 */ if (!ZGZ->r) return; ZGZM=ZGZ->M; for (i=0;ic;j++) /* Givens from left */ { x0=RM0[j];x1=RM1[j]; RM0[j]=cc*x0+ss*x1; RM1[j]=ss*x0-cc*x1; } for (j=0;jr;j++) /* Givens from right */ { RM0=ZGZM[j];x0=RM0[i];x1=RM0[i+1]; RM0[i]=cc*x0+ss*x1; RM0[i+1]=ss*x0-cc*x1; } } ZGZ->r--;ZGZ->c--; } void addconstraint(coninfile,confn,ZGZ,Ain,Af,Ac,I,R,Q,T,s,tk,newR) int coninfile;char *confn;matrix *ZGZ,Ain,Af,*Ac,I,*R,*Q,*T;long s,*tk; int newR; /* Adds a constraint to the working set Ac and performs updates of the factorisation matrices Q and T before obtaining R afresh,if newR non-zero. When adding a constraint, update is done using Givens rotations, in such a way that R can be easily updated. */ { matrix a,Gs,Gc; long r,c,q; FILE *in; char *errs; if (coninfile) { in=fopen(confn,"rb"); if (in==NULL) { errs=(char *)calloc(200,sizeof(char)); sprintf(errs,"%s not found, nothing read!",confn); ErrorMessage(errs,1); } fread(&r,sizeof(long),1,in); fread(&c,sizeof(long),1,in); fseek(in,(s*c*sizeof(double)+2*sizeof(long)),0); a=initmat(c,1L); fsaferead(a.M[0],sizeof(double),c,in); } else { c=Ain.c;r=Ain.r;a.c=c;a.r=1L;a.vec=1;a.M=Ain.M+s;a.V=a.M[0];} I.V[*tk]=(double)s;(*tk)++; getAc(coninfile,confn,Ain,Af,Ac,I,*tk); q=T->c-T->r-1; /* number of Givens transformations needed */ Gc=initmat(q,1L);Gs=initmat(q,1L); /* storage for Givens coefficients */ GivensAddconQT(Q,T,&a,&Gs,&Gc); if (newR) Rupdate(ZGZ,R,Gs,Gc); freemat(Gc);freemat(Gs); if (coninfile) { freemat(a); fclose(in); } } long feasibility(coninfile,name,Ain,x,b,maxviol) int coninfile;char *name;matrix Ain,x,b;double *maxviol; /* checks that x satisfies Ax>=b. */ { long i,j,k=1L,r,c; double z,fetol; matrix A; FILE *in; char *errs; *maxviol=0.0; fetol=TOL*enorm(x); if (coninfile) { in=fopen(name,"rb"); if (in==NULL) { errs=(char *)calloc(200,sizeof(char)); sprintf(errs,"%s not found, nothing read ! ",name);ErrorMessage(errs,1); } fread(&r,sizeof(long),1,in); fread(&c,sizeof(long),1,in); if ((c!=x.r)||(r!=b.r)) { fclose(in); ErrorMessage("Constraint matrix file incompatible with parameter vector.",1); return(0); } A=initmat(1L,x.r); } else { A.r=1L;A.c=x.r;c=Ain.c;r=Ain.r;A.vec=1;} /* A used as dummy row vector */ for (i=0;i0.0) { k=0L;if ((b.V[i]-z)> *maxviol) *maxviol=b.V[i]-z;} } if (coninfile) { fclose(in);freemat(A);} return(k); } double objective(F,f,J,da,a,prob_dat,G,Y,W,S,Q,gk,ro,lam,tk,getGJ,minimum) int (*F)(matrix,matrix,matrix,matrix,matrix,void *,int,double); matrix f,J,da,a,G,Y,W,S,Q,gk;double ro,lam;void *prob_dat;int getGJ,*minimum;long tk; /* This function is called by NonLinLS() to get the value of the objective function, the local gradient, the Jacobian, J etc. if getGJ!=0 it also tests for a minimum returning minimum=0 if there isn't one and minimum=1 if there is. * A returned *minimum value of -2 indicates a user termination (signalled by F returning a non-zero value). * Pass *minimum in as -1 to get an evaluation at 10 times regular accuracy (if F() supports this). This feature will not work with getGJ==1. * The value returned is: [(Y-f)'W(Y-f) + n*ro*a'Sa]*0.5 where n is the number of datapoints, and the 0.5 entirely escapes me. F() is the function that is called to run the model being fitted in order to produce the fitted values, f , that should approximate the data, Y. F() also returns a jacobian, J, when getGJ is set to a non-zero value. a is the vector of parameters to be obtained by fitting. da is the vector of finite difference intervals used to obtain J (these are obtained automatically in F()) G returned with J'WJ+ro*Y.r*S+ lam*I gk returned with gradient of objective w.r.t. a tk is number of active constraints prob_dat is a pointer used to pass in a problem specific structure containing information needed by the particular application. Typically this is caste to the correct type within F(). Q, da, G and J are only used if getGJ=1; */ { matrix rss,e,JW,Sa,D,resp,resb,ap,temp,pgf,pgb,WDp,WDb; double res=0.0,tol_fac; long i,j,k; e=initmat(Y.r,1L); D=initmat(J.r,J.c*2); if (*minimum==-1) tol_fac=0.1; else tol_fac=1.0; *minimum=0; if (getGJ>=1) tol_fac=1.0; i=(*F)(f,J,D,a,da,prob_dat,getGJ,tol_fac); // if F() returns 1 then user has halted it if (i==1) { *minimum=-2; freemat(e); freemat(D); return(1e300); } matrixintegritycheck(); for (i=0;i0.0) mad(G,G,S,1.0,Y.r*ro); /* Only include smoothness if its there ! */ for (i=0;i1e150/Y.r) { if (res<1e300) res=1e300;break;} /* avoids overflow on silly steps */ res+=W.V[i]*e.V[i]*e.V[i]; } else /* W is full matrix */ { rss=initmat(1L,1L); multi(3,rss,e,W,e,1,0,0); res=rss.V[0]; freemat(rss); } freemat(e); if (S.r) /* Only include smoothness if it's there ! */ { e=initmat(1L,1L); multi(3,e,a,S,a,1,0,0); res+=Y.r*ro*e.M[0][0];freemat(e); } res/=2.0; // the objective is finished.... if (getGJ==2) { qpoutdata.obj=res; } if ((getGJ)&&(getGJ!=2)) /* time to directly FD the objective and perform minimum test */ { *minimum=1; for (i=0;i 0.0 ) *minimum=0; /* because gradients were of same sign */ freemat(resp);freemat(resb);freemat(pgf);freemat(pgb); } freemat(D); return(res); } void LSQPaddcon(matrix *Ain,matrix *Q,matrix *T,matrix *Rf,matrix *Py,matrix *PX, matrix *s,matrix *c,int sth) /* Adds the sth row of Ain to the avtive set, updates Q and T using a sequence of T->c-T->r-1 Givens rotations from the right, coefficients of which are stored in s and c. The ith rotation acts on elements (i,i+1) (i=0,1,...). Updates the upper triangular (lower left 0) matrix Rf = PXQ, by applying the above Givens rotations from the right (updating Q) which introduces elements on the sub diagonal of Rf; these subdiaogonal elements are then zeroed using Givens rotations from the left, by way of updating P. Hence Py and PX can be updated at the same time. */ { matrix a; double RfMji,*RfV,*RfV1,ss,cc,r,x1,x2; int i,j,k; a.V=Ain->M[sth];a.r=Ain->c;a.c=1L; // vector containing sth constraint s->r=T->c-T->r-1; // number of Givens rotations about to be returned // Update Q and T and return Givens rotations required to do so .... GivensAddconQT(Q,T,&a,s,c); // Now apply the rotations from the right to Rf.... for (i=0;ir;i++) { cc=c->V[i];ss=s->V[i]; k=i+2;if (k>Rf->r) k--; for (j=0;jM[j]; RfMji=RfV[i]; RfV[i]=cc*RfMji+ss*RfV[i+1]; RfV[i+1]=ss*RfMji - cc*RfV[i+1]; } } /* Now zero the subdiagonal elements that have just been introduced, and apply the Givens rotations from the left, used to do this, to Py and PX */ for (i=0;ir;i++) // work through the extra subdiagonal elements { // this will act on rows i and i+1, zeroing i+1,i - work out coefficients RfV=Rf->M[i];RfV1=Rf->M[i+1]; x1=RfV[i];x2=RfV1[i]; r=sqrt(x1*x1+x2*x2);ss=x2/r;cc=x1/r; Rf->M[i][i]=r;Rf->M[i+1][i]=0.0; for (j=i+1;jc;j++) // apply rotation along the rows { x1=RfV[j];x2=RfV1[j]; RfV[j]=cc*x1+ss*x2; RfV1[j]=ss*x1-cc*x2; } // Apply this rotation to Py x1=Py->V[i];x2=Py->V[i+1]; Py->V[i]=cc*x1+ss*x2; Py->V[i+1]=ss*x1-cc*x2; // and apply the same rotation to PX for (j=0;jc;j++) // work along the rows { x1=PX->M[i][j];x2=PX->M[i+1][j]; PX->M[i][j]=cc*x1+ss*x2; PX->M[i+1][j]=ss*x1-cc*x2; } } } int LSQPstep(int *ignore,matrix *Ain,matrix *b,matrix *p1,matrix *p,matrix *pk) /* This is the stepping routine for the constrained least squares fitting routine. It should be faster than step, but more or less does the same thing. The return value is -1 for a minimum, otherwise the row of Ain containing the constraint to add is returned. ignore[i] should be set to 1 to ignore row i of Ain, to 0 to include it. Starting from p a step is taken to p+pk, if this would violate any constraints in the working set, then a step is taken from p along pk, to the closest constraint. The constraints are Ain p >= b. On exit: p1 contains the new parameter vector; the return value is -1 for a minimum, otherwise the constraint that needs to be added (i.e. the row of Ain) */ { double Ap1,ap,apk,alpha,alphamin,*AV,*pV,*p1V,*pkV; int imin,i,j; alphamin=1.0;imin= -1; p1V=p1->V;pV=p->V;pkV=pk->V; for (i=0;ir;i++) p1V[i]=pV[i]+pkV[i]; // step all the way to minimum for (i=0;ir;i++) // work through the constraints { AV=Ain->M[i]; if (!ignore[i]) // skip any already in working set { Ap1=0.0; for (j=0;jc;j++) Ap1+=AV[j]*p1V[j]; // form A p1 = A(p+pk) if ((b->V[i]-Ap1)>0.0) // does p+pk violate the ith constraint? { ap=0.0;apk=0.0; // working out quantities needed to find distance to constraint from p for (j=0;jc;j++) { ap+=AV[j]*pV[j]; apk+=AV[j]*pkV[j]; } if (fabs(apk)>0.0) { alpha=(b->V[i]-ap)/apk; // p + alpha*pk is on the ith constraint if (alphar;j++) p1V[j]=pV[j]+alphamin*pkV[j]; /* 2/2/97 - avoids distance calc for all that would violate full step */ } } } } } return(imin); } void LSQPdelcon(matrix *Q,matrix *T,matrix *Rf,matrix *Py,matrix *PX,int sth) /* This routine deletes row s from the active set matrix, A, say, where AQ=[0,T] and T is reverse lower triangular (upper left is zero). It updates Q and T using Givens rotations from the right. These rotations induce subdiagonal elements in Rf=PXQ from column Rf->c-T->r to column Rf->c-s+2, where T->r is the number of active constraints before deletion. Note however that the Givens rotations that update Q and T, have to be applied in an order that works back through the columns of Rf=PXQ - this has the potential to produce a triangular block of elements below the diagonal, if they are all applied before applying the update rotations for P. Hence the appropriate thing to do is to apply each rotation from the left to Rf, as it is obtained and then work out the Givens rotation from the left that will immediately zero the unwanted subdiagonal element - this being an update of P, which should immediately be applied to PX and Py. */ { int i,j,colj,coli,k,Tr,Tc,Qr,T1r,T1c; double r,s,c,xi,xj,**TM,**QM,**T1M,*TV,*QV,*T1V,*RfV,*RfV1; Tr=T->r;TM=T->M;QM=Q->M;Tc=T->c;Qr=Q->r; for (i=sth+1;iM[j]; // row to apply rotation to xi=RfV[coli]; RfV[coli]= -c*xi+s*RfV[colj]; RfV[colj]=s*xi+c*RfV[colj]; } // There is now an unwanted element at row colj, column coli // Calculate a rotation from the right that will zero the extra element xi=Rf->M[coli][coli];xj=Rf->M[colj][coli]; // xj to be zeroed r=sqrt(xi*xi+xj*xj); s=xj/r;c=xi/r; // Givens coefficients to zero xj into xi Rf->M[coli][coli]=r;Rf->M[colj][coli]=0.0; // Now apply to rest of row from column colj (column coli already done) RfV=Rf->M[coli];RfV1=Rf->M[colj]; for (j=colj;jc;j++) { xi=RfV[j];xj=RfV1[j]; RfV[j]=c*xi+s*xj; RfV1[j]=s*xi-c*xj; } // And apply this rotation from the right to Py and PX // Apply this rotation to Py xi=Py->V[coli];xj=Py->V[colj]; Py->V[coli]=c*xi+s*xj; Py->V[colj]=s*xi-c*xj; // and apply the same rotation to PX for (j=0;jc;j++) // work along the rows { xi=PX->M[coli][j];xj=PX->M[colj][j]; PX->M[coli][j]=c*xi+s*xj; PX->M[colj][j]=s*xi-c*xj; } } // Now actually remove the extra row from T - this could be done awefully efficiently // by shuffling the pointers to rows, but it would probably end in tears, so I haven't T->r--;T1M=T->M;T1r=T->r;T1c=T->c; for (k=0;k l'[0,T]=g'Q, and to find l, solve l'T=x, where x is the last tk=T->r rows of g'Q - this also yields the minimum of ||A'l-g||, which is appropriate. Note that T passed to the routine actually contains [0,T] and the first fixed_cons rows of T relate to the fixed constraints (if any). p1 and y1 are workspace matrices of length p->r and X->r respectively The routine returns -1 if there are no -ve multiplier estimates, otherwise it returns the index of *Inequlity* constraint with the most negative one. fixed[i] is set to 1 if the corresponding inequlity constraint is to be left in the active set regardless of lagrange multiplier - this is part of a strategy to avoid repeatedly deleting constraints wrongly. */ { int i,j,tk; double x; tk=T->r; vmult(X,p,y1,0); // form y1= Xp vmult(X,y1,p1,1); // form p1 = X'Xp for (i=0;ir;i++) p1->V[i]+= -Xy->V[i]; // form p1 = g = X'Xp - X'y // now create the last tk=T->r elements of g'Q and store in y1 for (i=0;iV[i]=0.0; for (j=0;jr;j++) y1->V[i]+=p1->V[j]*Q->M[j][Q->c-tk+i]; } // Now solve l'T=g'Q (where first tk rows of y1 contain g'Q).... for (i=tk-1;i>=fixed_cons;i--) // work down through the the lagrange multipliers { x=0.0;for (j=i+1;jV[j]*T->M[j][T->c-i-1]; if (T->M[i][T->c-i-1]!=0.0) p1->V[i]=(y1->V[tk-i-1]-x)/T->M[i][T->c-i-1];else p1->V[i]=0.0; } // Now look for the most negative multiplier for an inequlity constraint x=0.0;j=-1; for (i=fixed_cons;iV[i]V[i];} // if (j==-1) if (p1->V[i]V[i];} // only delete last constraint added if it has only -ve multiplier if (j!=-1) j -= fixed_cons; return(j); // returns index of inequality constraint to delete } /***************************************************************************/ /* Main Public Routines. */ /***************************************************************************/ void QPCLS(matrix *Z,matrix *X, matrix *p, matrix *y,matrix *Ain,matrix *b,matrix *Af,int *active) /* This routine aims to fit linearly constrained least squares problems of the form: min ||Xp-y||^2 subject to Ain p>=b and Af p = constant *without* forming X'X directly. By suitable redefinition of X and y it's easy to perform weighted and/or penalized regressions using this routine...... The routine uses working matrices T, Q, Rf, PX and working vectors Py, Xy, pz, pk, Pd In addition the routine creates workspace for the various service routines called by it, in order to avoid excessive memory allocation and deallocation. The Algorithm is as follows... 1. Form the QT factorisation of Af: Af Q = [0,T] T reverse lower triangular (i.e top left 0). Q contains column bases for the null and range spaces of Af: Q=[Z,Y]. Apply Q to X to get XQ(=[XZ,XY]). Form Q explicitly to give ready access to the null space basis Z. 2. Perform QR decomposition: XQ = P'Rf where P is orthogonal and Rf is upper triangular (lower left 0). Hence Rf= PXQ=[PXZ,PXY], as required. Apply P to y to get Py. Apply P to X to get PX. 3. Form Pd = Py-PXp, and solve: minimise || R pz - Pd ||^2, where R is the first p->r-tk-Af->r rows and columns of Rf. Solution occurs when R pz=x and x is the first p->r - tk - Af->r rows of Pd. (Note that Gill et al. get the sign wrong for Pd.) 4. Evaluate pk=Z pz, and step along it to minimum (goto 6.) or constraint. 5. Add constraint to working set: update QT factorisation; update Rf; update Py and PX. Return to 3. 6. Evaluate Lagrange multipliers l where Ac'l=g and g=X'Xp-X'y - Ac is the active constraint matrix. Clearly g involves X'X, which is unfortunate, but I can't figure out a way around it - however, it is only the signs of l that matter, so hopefully this is not critical. If multipliers are all +ve goto 8. otherwise proceed.... 7. Delete the constraint with the most -ve multiplier, updating Q, T, Rf, Py and PX at the same time. Return to 3. 8. Convergence! A minimum has been achieved. Free the workspace matrices and vectors and the indexing arrays, obtain Z, and return. On exit active[] contains the number of active inequlity constraints in active[0], and the row number of these constraints in Ain in the remaining elements of active[], active must be initialized to length p.r+1 on entry. See documentation in service routines: LSQPlagrange(); LSQPaddcon(); LSQPdelcon(); (above) Rsolv() (in matrix.c) for further details on steps 6, 5, 7 and 3. The approach is taken from Gill, Murray and Wright (1981) Practical Optimization page 180-181 Section 5.3.3. (But note wrong signs on p181 first display equation and definition of d_k) Routine has been tested against less numerically stable alternative using QP(). 20/11/99 */ { matrix Q,T,Rf,PX,Py,a,P,p1,s,c,Xy,y1,u,Pd,pz,pk; int k,i,j,tk,*I,*ignore,iter=0,*fixed,*delog,maxdel=100; double x; I=(int *)calloc((size_t) p->r,sizeof(int)); // I[i] is the row of Ain containing ith active constraint fixed=(int *)calloc((size_t) p->r,sizeof(int)); // fixed[i] is set to 1 when the corresponding inequlity constraint is to be left in regardless of l.m. estimate ignore=(int *)calloc((size_t) Ain->r,sizeof(int)); // ignore[i] is 1 if ith row of Ain is in active set, 0 otherwise delog=(int *)calloc((size_t) Ain->r,sizeof(int)); // counts up number of times a constraint is deleted p1=initmat(p->r,1L); // a working space vector for stepping & lagrange y1=initmat(y->r,1L); // a work space vector for lagrange s=initmat(p->r,1L);c=initmat(p->r,1L); // working space vectors for Givens rotation Xy=initmat(p->r,1L); // vector storing X'y for use in lagrange multiplier calculation vmult(X,y,&Xy,1); // form X'y Rf=initmat(X->r,X->c); // Rf=PXQ, where P and Q are orthogonal mcopy(X,&Rf); // initialize Rf while P and Q are identity matrices T=initmat(p->r,p->r); // initialised to max possible size Q=initmat(p->r,p->r); // required for access to Z for null space to full space transform // initialize Q, T and Rf using fixed constraints (if any) .... for (i=0;ir;i++) for (j=0;jr;j++) Q.M[i][j]=0.0; for (i=0;ir;i++) Q.M[i][i]=1.0; T.r=0L;a.r=1L;a.c=Af->c; for (i=0;ir;i++) { a.V=Af->M[i]; T=addconQT(&Q,T,a,&u); // adding constraint from Af to working set Hmult(Rf,u); // updating Rf (=XQ, at present) freemat(u); // freeing u created by addconQT() } // Now Form Rf, proper. i.e. PXQ, using QR factorization P=initmat(Rf.c,Rf.r); QR(&P,&Rf); // Rf now contains Rf=PXQ (on entry it contained XQ) Py=initmat(y->r,1L);mcopy(y,&Py); OrthoMult(&P,&Py,0,(int)P.r,0,1,1); // Form Py PX=initmat(X->r,X->c);mcopy(X,&PX); OrthoMult(&P,&PX,0,(int)P.r,0,1,1); // Form PX freemat(P); // no longer needed P=initmat(b->r,1L); // used solely for feasibility checking Pd=initmat(y->r,1L);pz=initmat(p->r,1L);pk=initmat(p->r,1L); tk=0; // The number of inequality constraints currently active printf("\nLSQ"); while(1) { iter++; // Form Pd=Py-PXp and minimize ||R pz - Pd|| vmult(&PX,p,&Pd,0); // Pd = PXp for (i=0;ir-tk-Af->r; // Restrict attention to QR factor of PXZ for (i=0;ir;Rf.c=X->c; // Restore Rf pz.r=p->r-tk-Af->r; // Find pk = Z pz, the search direction for (i=0;i-1) // add a constraint to the working set and update Rf, Py and PX { I[tk]=k;ignore[k]=1; // keeping track of what's in working set LSQPaddcon(Ain,&Q,&T,&Rf,&Py,&PX,&s,&c,k);tk++; if (delog[k]>maxdel) fixed[tk-1]=1; printf("+"); } else // it's a minimum - check lagrange multipliers { k=LSQPlagrange(X,&Q,&T,p,&Xy,&p1,&y1,fixed,(int)Af->r); if (k>-1) // then a constraint must be deleted { LSQPdelcon(&Q,&T,&Rf,&Py,&PX,k+(int)Af->r); // the Af.r added to k ensures that correct row of T deleted printf("-"); // update the fixed constraint list { for (i=k;i-1) // updating indexing arrays { ignore[I[k]]=0; delog[I[k]]++; for (i=k;iV[i]V[i]; printf("P\n Worst feasibility violation %g",x); // create Z - this version is a full null space matrix, rather than sequence of rotations *Z=Q; Z->c -= tk; // copy active constraint information to active active[0]=tk; for (i=0;i=b & Af p = "a constant vector" ...where B is a sum of m S[i] matrices multiplied by smoothing parameters theta[i]. The S[i]'s may be smaller than B (p->r by p->r) so S[i] is added to B starting at row and column off[i]. B must be non-negative definite, which means that the S[k]'s must be. W is the diagnoal matrix having w on the leading diagonal. In many applications the ith element of w will be the reciprocal of the variance associated with the ith element of i. The routine uses the fact that the problem can be re-written as.... minimise || Fp - z ||^2 Subject to Ain p >= b Af p = constant ... where F = [ X'W^0.5, B^0.5']' and z = [y'W^0.5, 0]'. This rewrite is performed and then LSQP is called to obtain the solution. If H->r==y->r on entry, then an influence (or "hat") matrix is returned in H. At present the calculation of H is inefficient and none too stable. On exit active[] contains a list of the active inequlity constraints in elements 1->active[0]. This array should be initialized to length p.r+1 on entry. 20/11/99 */ { int i,j,k; matrix z,F,W,Z,B,C; double x,xx; // form transformed data vector z z=initmat(y->r+p->r,1L);W=initmat(w->r,1L); for (i=0;ir;i++) { W.V[i]=sqrt(w->V[i]);z.V[i]=W.V[i]*y->V[i];} // form transformed design matrix X F=initmat(z.r,p->r); // first put in W^0.5X for (i=0;ir;i++) for (j=0;jc;j++) F.M[i][j]=W.V[i]*X->M[i][j]; // add up the Penalties B=initmat(p->r,p->r); for (k=0;kr rows of F for (i=0;ir][i]=C.M[i][j]; freemat(B);freemat(C); printf("\ncond(F)=%g",condition(F)); // Which means that the problem is now in a form where QPCLS can solve it.... QPCLS(&Z,&F,p,&z,Ain,b,Af,active); // note that at present Z is full not HH if (H->r==y->r) // then calculate the influence matrix XZ(Z'F'FZ)^{-1}Z'X'W { freemat(W);W=initmat(Z.c,Z.c); multi(4,W,Z,F,F,Z,1,1,0,0);invert(&W); // Wildly inefficient!! multi(5,*H,*X,Z,W,Z,*X,0,0,0,1,1); // ditto for (i=0;ir;i++) for (j=0;jc;j++) H->M[i][j]*=w->V[j]; } // working out value of objective at minimum B=initmat(z.r,1L);matmult(B,F,*p,0,0); xx=0.0;for (i=0;i= b and Af y=b using an active set method as described in Gill, Murray and Wright (1981) VARIABLES PASSED AS POINTERS ZGZ contains the matrix Z'GZ. It is initialised within the routine. Do not free between calls. Unlimited modification between calls is allowed. Z will contain the basis for the null space at the solution. Z is created by the routine, and re-created on subsequent calls. Do not free between calls; alteration is ok. 25/11/97 modified to output Z as householders if fullZ=0 below. Set fullZ=1 for old version. Each row of Z contains one of the householder rotation vectors required to construct the null space of the active constraints. NOTE that with efficient Z storage, Z is NOT what was actually used to produce ZGZ. If you form Z'GZ explicitly you will not get exactly ZGZ. In itself this doesn't matter, but if you use ZGZ in something like Z(Z'GZ)^{-1}Z' will NOT get the expected answer! If in doubt, reform ZGZ outside this routine - it is only not re-formed here for efficiency reasons! VARIABLES PASSED BY VALUE Af is the matrix for the fixed constraints. If there are no fixed constraints then set Af.r=0L. Ain is the matrix for inequality constraints. Need not be initialized if inequality constraints are to be supplied in a file. b is the vector of inequality constraints y is the vector w.r.t. which minimisation is taking place. It must be initialized so that none of the constraints are violated. G is the matrix in the quadratic form to be minimised cT is the vector in the quadratic form to be minimised confn is the string containing the name of the constraint file if Ain is not being used conon is set to 1 if the inequality constraints are to be imposed ) if not reset is set to 1 when the routine is first called and whenever the old null space is to be discarded. It must be set to 1 if the constraint matrices have changed or the size of the problem has changed. Set to zero to start with the same null space as last time. Set to 2 if only cT has changed. coninfile set to 1 if the inequality constraints are in the file whose name is in confn. Set to 0 if the inequlity constraints are in Ain. maxviol returns the maximum constraint violation returns -1 on failure through lack of +ve definiteness ******************************************************************************/ { long i,s,j,constrainedmin=0L; double k; static int iterate=0,first=1,fullZ=0, #ifdef QP_TEXT_OUT op=1; #else op=0; #endif static matrix Q,T,I,Ac,R; static long tk; matrix gk,pk,a,u; static matrix delog, /* stores number of deletions of a constraint to eliminate cycling */ ZGZp,Zp; /* These store old matrices - which are freed on reset */ if (op) printf("\nQ"); gk=initmat(G.r,1L);pk=initmat(G.r,1L); if (reset==1) { tk=Af.r; if (!first) { freemat(Q);freemat(T);freemat(I);} Q=initmat(G.r,G.c); T=initmat(y.r,y.r); T.r=tk; I=initmat(y.r,1L); } if (!first&& reset!=2) { freemat(ZGZp);freemat(Ac);freemat(R);} if (first) { first=0;delog=initmat(b.r,1L); } else freemat(Zp); for (i=0;iM[i][j]=G.M[i][j];Q.M[i][j]=0.0;} for (i=0;ir--;ZGZ->c--; } R=initmat(ZGZ->r,ZGZ->r); if (!chol((*ZGZ),R,0,0)) /* failure through non +ve definiteness */ { freemat(gk);freemat(pk);Zp=(*Z)=initmat(1L,1L); return(-1L); } } iterate++; if (op) printf(" %d ",iterate); while(1) { if (constrainedmin) { if (!conon) s= -1L; else s=lagrange(gk,Q,T,G,cT,y,I,delog,Af,1); if (s>=0) { k=(long)I.V[s]; if (op) {printf("-");fflush(stdout);} deleteconstraint(ZGZ,gk,pk,&R,G,cT,Q,&Ac,I,&T,y,s,&tk,1,1); I.V[tk]=k;/*tk++;*/ /* prevents a step of zero to the sth constraint */ s=step(coninfile,confn,Q,Ain,Af,I,pk,y,b,tk);/*tk--;*/ if (s>=0) delog.V[s]+=1.0; } else { if (fullZ) { Zp=(*Z)=initmat(G.r,G.c-tk); for (i=0;ir;i++) for (j=0;jc;j++) Z->M[i][j]=Q.M[i][j]; } else /* efficient Z storage */ { Zp=(*Z)=initmat(G.r,G.c); u=initmat(Ac.r,Ac.c); for (i=0;ir=Ac.r; freemat(u); } /*freemat(Ac);freemat(R); no longer deleted to allow recalc. with new c */ freemat(gk);freemat(pk); if (conon) feasibility(coninfile,confn,Ain,y,b,maxviol); if (op) {printf("P");fflush(stdout);} #ifdef OS2 _heapmin(); #endif /* The following packs the redundent rows and columns used for ZGZ storage with zeros. This can be useful later when obtaining Influence matrices */ return(tk-Af.r); } } else { searchdirection(gk,pk,R,G,Q,cT,y,tk,1); if (conon) s=step(coninfile,confn,Q,Ain,Af,I,pk,y,b,tk); else for (i=0;i=0)) delog.V[s]+=1.0; } if ((s>=0)&&(conon)) { if (op) {printf("+");fflush(stdout);} addconstraint(coninfile,confn,ZGZ,Ain,Af,&Ac,I,&R,&Q,&T,s,&tk,1); constrainedmin=0; } else constrainedmin=1; } } matrix getprojectedgradient(gk,Q,tk) matrix gk,Q;int tk; /* returns a vector pz containing the gradient gk projected into the current null space, whose basis is given by the the first Q.c-tk columns of Q */ { int i,j; matrix pz; pz=initmat(Q.c-tk,1L); for (i=0;i=0) for (i=0;i=f0) /* step was no use so it must be contracted */ { xb=1.0;xa=0.0;fa=f0;s=-1L; while ((fb>=fa)&&(xb>0.00000001)) { xc=xb;fc=fb;xb/=1.2; for (i=0;i can not hit a constraint */ fb=objective(F,fdum,J,dum,anew,prob_dat,G,Y,W,S,Q,gk,ro,0.0,tk,0,&qq); if (qq==-2) { *gotone=-1;return(1e300);} // signal user stop } for (i=0;i=0) /* then still heading downhill when last constraint encountered */ { xb=0.999*xc+0.001*xa; for (i=0;i=0) { z=enorm(pk); for (i=0;i=0 then step to constraint, otherwise, failed to find a minimum */ if (!ok) /* returning straight away */ { if (s>=0) /* stopped by a constraint */ { *sc=s; /* the constraint number */ *gotone=1; /* signals a downward step */ return(fc); /* returning the new low */ } else /* could not reduce the objective */ { *sc=-1L; /* no constraint */ *gotone=0; /* signals NO downward step */ return(f0); /* returning the old low */ } } else /* line minimisation by Brents method (after Press et al. 1988) */ { if ((faxb)||(xb>xc)) { sprintf(str, "Brent search initial triplet invalid\n\ fa-fb=%g fc-fb=%g\n\ xc-xb=%g xb-xa=%g",fa-fb,fc-fb,xc-xb,xb-xa); ErrorMessage(str,0); // DEBUG: error non-fatal to allow follow up } /* checking that there is a valid triplet with which to start brent */ x=w=v=xb; fw=fv=fx=fb; a=xa;b=xc; for (iter=1;iter<100;iter++) { xm=0.5*(a+b); tol2=2.0*(tol1=tol*fabs(x)+zeps); if (fabs(x-xm) <= (tol2-0.5*(b-a))) /* testing if finished */ { /* final return values */ if (xc==x) *sc=s; else *sc=-1L; *gotone=1; for (i=0;itol1) /* then fit a parabola to the best so far and minimise */ { r=(x-w)*(fx-fv); q=(x-v)*(fx-fw); p=(x-v)*q-(x-w)*r; q=2.0*(q-r); if (q>0.0) p = -p; q=fabs(q); etemp=e;e=d; /* now test whether or not parabola will do */ if (fabs(p) >= fabs(0.5*q*etemp) || p <= q*(a-x) || p >= q*(b-x)) d=Cgold*(e=(x >= xm ? a-x : b-x )); /* golden section */ else /* parabolic */ { d=p/q; u=x+d; if (u-a < tol2 || b-u < tol2) { if ((xm-x)>0.0) d=fabs(tol1); else d= -fabs(tol1);} } } else { d=Cgold*(e=(x>= xm ? a-x : b-x )); } u=(fabs(d) >= tol1 ? x+d : x+ (d>0.0 ? fabs(tol1) : - fabs(tol1))); for (i=0;i= x) a=x; else b=x; v=w;w=x;x=u; fv=fw;fw=fx;fx=fu; } else { if (u=b & Af a = const Where F is a non-linear function of the parameter vector a and it is possible to calculate: _ _ | dF /da dF /da . . . . | | 0 0 0 1 | | dF /da dF /da . . . . | J= | 1 0 1 1 | | | | . . | | . . | |_ . . _| The user must supply the routine void F(f,J,a,afix,getJ) matrix f,J,a,afix;int getJ; which returns the model values in f and if getJ!=1 the jacobian in J, using the parameters in a and afix. The Parameters F pointer to function F described above. Coa this is a matrix which will contain the posterior covariance matrix of the parameter vector DIVIDED BY the error variance. It should be initialised as an a.r by a.r matrix. J this is a matrix containing the Jacobian on exit Z pointer to the current null space basis. f vector of model approximations to Y. a vector of variable parameters. afix vector of parameters that aren't being fitted. W weight matrix for the Y's. S quadratic objective matrix (often a smoothness term) Y the data vector. A matrix in the inequality constraints Aa>=b. b see above. ro the 'smoothing parameter' controlling the trade off between the least squares term and the quadratic objective. maxviol returns maximum constraint violation reset is no longer used (set to 1 if the constraints have changed or you want to re-start abandoning the old null space.) * returns 1 for user termination and 0 otherwise * Initializes Z and returns null space basis in it, as a series of Householder rotations from which the null space basis can be efficiently reconstructed. Note that internally this routine represetns the null space basis explicitly, i.e. using a matrix the columns of which are a basis for the null space of the active constraints. */ { long i,j,s,iter=0L,dels=0,adds=0,lastdeletion=-1L; double f0,f1,Tf=1e-6,fmin,fmin0,fmin1,interr,**hisa,*hisf; static int iterate=0,hislag=4,hisp; int mincount=0,itisamin=0,ok,gotone,maybe,tightfit=0,nosig,nscount=0; matrix G,Ac,R,gk,pz,da,dafd,pk,pk0,pk1,anew,anew0,anew1,dum,fdum,amin,M,ZGZ; static matrix Q,T,I,delog; /* stores number of deletions of a constraint to eliminate cycling */ static long tk=0L; dum.M=NULL;dum.r=0L;dum.c=0L;M.r=0L; dafd=initmat(a.r,a.c); if (tightfit) hislag=10; else hislag=4; amin=initmat(a.r,1L); if (!feasibility(0,"",A,a,b,maxviol)) { ErrorMessage("Initial point not feasible.",1);} G=initmat(a.r,a.r);gk=initmat(G.r,1L); pk=initmat(G.r,1L);pk0=initmat(G.r,1L);pk1=initmat(G.r,1L); da=initmat(G.r,1L);fdum=initmat(f.r,f.c); anew=initmat(a.r,1L);anew0=initmat(a.r,1L);anew1=initmat(a.r,1L); tk=Af.r; Q=initmat(G.r,G.c);for (i=0;i=0) { addconstraint(0,"",&ZGZ,A,Af,&Ac,I,&R,&Q,&T,s,&tk,0); delog.V[s]+=1.0;adds++; if (lastdeletion==s) /* some evidence of cycling */ if (itisamin) delog.V[s]+=DELMAX+200.0; /* cycling at proper minimum */ else delog.V[s]+=DELMAX+10.0; /* cycling only on approx min */ } for (i=0;isqrt(Tf)*fabs(a.V[i])) ok=0; if ((ok)&&(f0-f14) itisamin=6; f0=f1; /* If convergence is slowing down and there are constraints in the active set, then it is worth getting approximate lagrange multipliers, before full convergence criteria are met, since there is no point polishing a minimum that will change with the deletion of an active constraint - this is what the following code tries to establish - if maybe ends up at 1 then the minimum in the current space is deemed `good enough' to try working out LMs - note that the estimates are only linear and the usual problems of estimating LMs away from the `true' minimum apply */ if (!itisamin) { maybe=1; /* less rigorous test for minimum to allow approx LM estimates */ for (i=0;i0.005*fabs(a.V[i])) maybe=0; if ((fabs(f1-f0)>1e-5*fabs(f0+f1))) maybe=0; if (tk==0) maybe=0; if (s>=0) maybe=0; } if (nscount>3) itisamin=7; if ((!gotone)||(itisamin)||(tk==a.r)||(maybe)||nosig) { mincount=0; if (itisamin) /* its a bonafide minimum, so unfix constraints that cycled, moved from after deleteconstraint 19/8/96 */ for (i=0;i DELMAX + 10.0)&&(delog.V[i]< DELMAX +200.0)) delog.V[i] -= DELMAX + 10.0; s=lagrange(gk,Q,T,G,dum,a,I,delog,Af,0); /* lo, a minimum */ if (s>=0) { /* make a note of the deleted constraint */ lastdeletion=(long)round(I.V[round(s)]);dels++; deleteconstraint(&ZGZ,gk,pk,&R,G,dum,Q,&Ac,I,&T,a,s,&tk,0,0); } else if (itisamin) /* full blown minimum, not just a trial of L-Ms */ { break; // leave while loop } } } } if (gotone==-1) // then user has signalled stop { j=1; } else // Get null space in compact form suitable for efficient calcualtion outside routine { (*Z)=initmat(Ac.c,Ac.c); QT(*Z,Ac,0);Z->r=Ac.r; } freemat(ZGZ); freemat(amin);freemat(dafd); freemat(fdum);freemat(delog); freemat(anew);freemat(anew0);freemat(anew1);freemat(Ac);freemat(da); freemat(gk);freemat(pk);freemat(pk0);freemat(pk1);freemat(G); for (i=0;i0.0) for (j=0;j0||iniJWJrS==0) f0=objective(F,f,J,dafd,a,prob_dat,G,Y,W,S,Q,gk,ro,0.0,tk,(int)i,&itisamin); if (itisamin==-2) // user stop { gotone=-1; break; } itisamin=-1; // signal that increased accuracy required.... fmin0=objective(F,f,J,dafd,a,prob_dat,G,Y,W,S,Q,gk,ro,0.0,tk,0,&itisamin); if (itisamin==-2) // user stop { gotone=-1; break; } interr=fabs(f0-fmin0); // .... to work out accuracy (usually integration accuracy) qpoutdata.obj=f0; qpoutdata.constraints=tk; if ((iter)&&update) /* BFGS update (in current Null space) */ { Q.c -= tk; /* treat as Z */ pz.r -= tk; matmult(pz,Q,pk,1,0); multi(4,sBs,pz,L,L,pz,1,0,1,0); mad(v,gk,gk0,1.0,-1.0); u.r -= tk; matmult(u,Q,v,1,0); /* u contains projected gradient difference */ v.r -=tk; multi(3,v,L,L,pz,0,1,0); sp=dot(u,pz); if (sp<=0.0) { sprintf(errs,"CQN -ve sp = %g\n",sp); /* => update not +ve definite */ // ErrorMessage(errs,0); } else { choleskir1ud(L,u,1.0/sp); choleskir1ud(L,v,-1.0/sBs.V[0]); } v.r+=tk;u.r+=tk;Q.c+=tk;pz.r+=tk; } if (s>=0) /* add an inequality constraint to the working set */ { addconstraint(0,"",&ZGZ,A,Af,&Ac,I,&L,&Q,&T,s,&tk,1); } if (itisamin || !decrease || notsig) { s=lagrange(gk,Q,T,G,dum,a,I,delog,Af,0); if (s>=0) { deleteconstraint(&ZGZ,gk,pk,&L,G,dum,Q,&Ac,I,&T,a,s,&tk,0,1); for (i=0;i3)) break; } Q.c -= tk; /* sets Q to Z - the null space column basis */ u.r -= tk; /* using this for the projected gradient */ matmult(u,Q,gk,1,0); for (i=0;i4.0*fabs(dafd.V[i])) { update=1;break;} } if (s>-1) decrease=1; if (decrease) for (i=0;i=b and Af a = const where f(a) is some nonlinear function, whose Jacobian can be evaluated. Algorithm defines: z=y-f+Ja where all are evaluated at current a and seeks to minimise (z-Ja)'W(z-Ja) + a'Sa subject to the constraints, to find a new vector a. The process is iterative. * The QP code used is that designed specifically for least squares problems. * On exit the Z contains the null space of the active constraints as a sequanece of Householder rotations stored in Z. Because this routine may have adjusted the step length after the end of the last QP step the active set of constraints is re-checked before Z is formed. * Returns 1 on user termination, 0 otherwise */ { matrix z,dafd,Afp,cT,G,p,Q,gk,anew,fdum,dum,H; static matrix ZGZ; // this is unsatisfactory - need to replace int j,iter,itisamin=0,down,m,off=0,*active; long tk,*aliased,n,i,k; double f0,f1,fmin=0.0,scale,lam=1.0,xx,yy; tk=0L;dum.M=NULL;dum.r=0L;dum.c=0L; G=initmat(a.r,a.r); Afp=initmat(J.c+Af.r,a.r); /* Afp allows extra fixed constraints to be added in */ Afp.r=Af.r; for (i=0;if0) { fmin=f0;down++; // next redo f,J,G..... f0=objective(F,f,J,dafd,anew,prob_dat,G,y,W,S,Q,gk,ro,0.0,tk,1,&itisamin); if (itisamin==-2) break; } else // try shorter step { scale=1.0; for (i=0;i<15;i++) { scale*=0.8; for (k=0;k0) /* then some parameters are aliased */ { Afp.r=Af.r+n; for (i=0;ir=Afp.r; // Final run for final J and forced o/p if (itisamin!=-2) { f0=objective(F,f,J,dafd,a,prob_dat,G,y,W,S,Q,gk,ro,0.0,tk,2,&itisamin);} if (itisamin==-2) // then it's a user stop { j=1; } else { j=0; } freemat(G);freemat(Afp);freemat(anew);freemat(p);freemat(z);freemat(gk); freemat(cT);freemat(dafd);freemat(fdum);free(active); return(j); } void TotalSmooth(matrix *St,matrix *S,long *off,double *theta,long n,int m) { int i,l,j; for (i=0;ir;i++) for (j=0;jc;j++) St->M[i][j]=0.0; for (l=0;lM[off[l]+i][off[l]+j]+=S[l].M[i][j]*theta[l]/n; } double slowtrace(matrix X,matrix S) { int i,j; double tr=0.0; matrix XX,XXX,XXXX; XX=initmat(X.c,X.c);matmult(XX,X,X,1,0); for (i=0;i= b A_f a = constant. Algorithm defines pseudodata z=y-f+Ja and solves: ||W^{0.5} (z-Ja) ||^2 + \sum \lambda_i a' S_i a subject to constraints as well as estimating the \lambda_i's. The basic loop to iterate is this: 1. Estimate updated smoothing parameters given current model parameters 2. Update model parameters: i) given old smoothing parameters. ii) given new smoothing parameters. ... calculating gcv scores for both. 3. Accept the smoothing parameter, model parameter pair with the lowest gcv score if both are acceptable, acceptable means that model parameter update has not increased the objective (given corresponding smoothing parameters). 4. Terminate if the update doesn't change the smoothing parameters or model parameters. Notes: *lmax is a vector of maximum values for the smoothing parameters in lam. if lmax[i] <=0.0 then no upper bound is put on lam[i].... * Returns *trace==-1 to signal a user termination. * Before returning the null space of any current constraints is obtained and put in *Z as a series of Householder rotations. * objective assumes that S_t= \sum \lambda_i S_i / n where n is number of data, but gcv routines and PCLS() do not use this factor of n. The s.p.s returned in lam do not assume that the factor of n is used in the calculation of St. */ { matrix z,dafd,Afp,G,p,Q,gk,anew,fdum,dum,St,at,H,At,a0,a1,J0,J1,fv0,fv1; int l,j,iter,itisamin=0,down,bayesian=1,maxiter=1000,*active,*active0,*active1,stop=0; long tk=0L,*aliased,n,i,k; double maxviol,tr,f0,f1,fmin=0.0,*theta,*otheta,sig2=-1 /* signals GCV to multismooth */ ,dr=0.0,xx,yy,f00,f01,f02,f10,f11,f12,cv0,cv1; /* damping ratio for theta update */ n=y.r; dum.M=NULL;dum.r=0L;dum.c=0L; G=initmat(a.r,a.r); At=initmat(a.r,a.r); // matrix for holding active set for Z calculation *Z=initmat(a.r,a.r); // matrix for holding Z - returned by this routine Afp=initmat(J.c+Af.r,a.r); /* Afp allows extra fixed constraints to be added in */ Afp.r=Af.r; for (i=0;i0) // then some parameters are aliased { Afp.r=Af.r+n; for (i=0;if01) break; f01=f02; } if (itisamin==-2) break; // user break - so stop altogether if (f02>f01&&i) // set step to best achieved { for (j=0;jf00) { mcopy(&a,&a0);f01=f00;} // no improvement by any step if (iter) // then try step using new theta estimates { for (i=0;if11) break; f11=f12; } if (itisamin==-2) break; // user break - so stop altogether if (f12>f11&&i) // set step to best achieved { for (j=0;jf10) { mcopy(&a,&a1);f11=f10;} // no improvement managed if (f11r=At.r; } // output stuff qpoutdata.obj_change=f0-f1; qpoutdata.obj=f1; qpoutdata.constraints=Z->r; if (stop) break; // convergence has occured, J, Z, f and theta are consistent /* now re-estimate smoothing parameters */ for (i=0;i0.0&&theta[i]>lmax[i]) theta[i]=lmax[i]; // constrain s.p. to upper limit } // This is the end of the iterative loop. Z is correct unless iter==maxiter if (iter==maxiter) ErrorMessage("Failure to converge in optNLLS().",0); if (itisamin==-2) // then user stopped { *trace=-1.0; // NOTE: code needed here } else { for (i=0;i