/* $Revision: 2.0 $ $Date: 2002/10/11 18:54:54 $ */ /*========================================================= * csminwel.c * * Unconstrained minimization. Uses a quasi-Newton method with BFGS update of * the estimated inverse hessian. It is robust against certain pathologies * common on likelihood functions. It attempts to be robust against "cliffs", * i.e. hyperplane discontinuities, though it is not really clear whether what * it does in such cases succeeds reliably. * * function [fhat,xhat,ghat,Hhat,itct,fcount,retcodehat] = csminwel(fcn,x0,H0,grad,crit,nit,varargin) * fcn: string naming the objective function to be minimized * x0: initial value of the parameter vector * H0: initial value for the inverse Hessian. Must be positive definite. * grad: Either a string naming a function that calculates the gradient, or the null matrix. * If it's null, the program calculates a numerical gradient. In this case fcn must * be written so that it can take a matrix argument and produce a row vector of values. * crit: Convergence criterion. Iteration will cease when it proves impossible to improve the * function value by more than crit. * nit: Maximum number of iterations. * varargin: A list of optional length of additional parameters that get handed off to fcn each * time it is called. * Note that if the program ends abnormally, it is possible to retrieve the current x, * f, and H from the files g1.mat and H.mat that are written at each iteration and at each * hessian update, respectively. (When the routine hits certain kinds of difficulty, it * write g2.mat and g3.mat as well. If all were written at about the same time, any of them * may be a decent starting point. One can also start from the one with best function value.) * * Note: the level of display output depends on the value of the variable * "verbose" defined in the base, caller's or global workspaces * 0 - no output * 1 - only warnings * 2 - all output * * MATLAB algorithm by Christopher Sims * C implementation by Iskander Karibzhanov * Master of Science in Computational Finance * Georgia Institute of Technology * * This is a MEX-file for MATLAB. * Copyright 1999 Christopher Sims * Copyright 2002 Federal Reserve Bank of Atlanta *======================================================= * Revision history: * * 10/3/2002 - 1. corrected problem with memory corruption in C-MEX-file (csminwelmex.c) * (needed to switch fcnRhs[0] back to x[0] before destroying it. * If we don't do this, we will later clear previously destroyed array * (one of x[1], x[2] or x[3]) which causes memory fault. * The reason why fcnRhs[0] pointed to array other than x[0] is * because we use mxSetPr in feval and gfeval. * This was not a problem in C-file (csminwel.c). * * 10/11/2002 - 1. changed csminit function to avoid using fPeak without first initializing it * 2. added two switches in csminit function to assign retcode to 7 for lambda>=4 * 3. added one more verbose level to display only warnings or all output * *=======================================================*/ /* numgrad parameters */ #define SCALE 1 /* csminit parameters */ #define ANGLE .005 #define THETA .3 #define FCHANGE 1000 #define MINLAMB 1e-9 #define MINDFAC .01 #include #include #include #include "mex.h" static int n, nn, npar; static char fcn[100]=""; static char gfcn[100]=""; static mxArray **fcnRhs, *fcnLhs, *gfcnLhs[2]; static bool ngrad=true; static int verbose=0; void numgrad(double *g, bool *badg, double *x); double feval(double *x); /* x must point to the heap */ void gfeval(double *g, bool *badg, double *x); /* x must point to the heap */ void csminit(double *fhat, double *xhat, int *fcount, int *retcode, double *x0, double f0, double *g, bool badg, double *H0); double times(const double *x, const double *y); void peakwall(double *g, bool *badg, int retcode, double *x); void bfgsi(double *H, const double *dg, const double *dx); double *mtimes(const double *x, const double *y); double *mminus(const double *x, const double *y); /* plhs = [fh,xh,gh,H,itct,fcount,retcodeh] */ /* prhs = [fcn,x0,H,grad,crit,nit,varargin] */ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { bool done=false, badg[4], badgh, nogh=true, stuck=false; double *x[4], *g[4], f[4], *grad, crit, *dg, *dx; double *fh, *xh, *gh, *H, *itct, *fcount, *retcodeh; int retcode[3], fc=0, ih, nit; register int i; if (nrhs < 6) mexErrMsgTxt("At least six input arguments required."); if (nlhs != 7) mexErrMsgTxt("Seven output arguments required."); if ((fcnLhs=mexGetVariable("global","verbose")) || (fcnLhs=mexGetVariable("caller","verbose")) || (fcnLhs=mexGetVariable("base","verbose"))) verbose = (int)mxGetScalar(fcnLhs); n = mxGetM(prhs[1]); nn = n*n; /* ngrad=0 if grad is a non-empty string */ if (!mxIsChar(prhs[3])) { if (!mxIsEmpty(prhs[3])) grad = mxGetPr(prhs[3]); } else if (!mxIsEmpty(prhs[3])) { if (mxGetString(prhs[3], gfcn, mxGetM(prhs[3])*mxGetN(prhs[3])+1)) mexWarnMsgTxt("Not enough space. Gradient function name is truncated."); ngrad = false; } for (i=0; i<4; i++) g[i] = mxCalloc(n, sizeof(double)); if (mxGetString(prhs[0], fcn, mxGetM(prhs[0])*mxGetN(prhs[0])+1)) mexWarnMsgTxt("Not enough space. Function name is truncated."); npar = nrhs-5; fcnRhs = mxCalloc(npar, sizeof(*fcnRhs)); x[0] = mxGetPr(fcnRhs[0]=mxDuplicateArray(prhs[1])); for (i=1; i<4; i++) x[i] = mxCalloc(n, sizeof(double)); for (i=6; i 1e50) { mexWarnMsgTxt("Bad initial parameter"); return; } crit = mxGetScalar(prhs[4]); nit = (int)mxGetScalar(prhs[5]); if (ngrad) /* if grad is not string, either compute it (if grad=[]) or check whether it is fine) */ if (mxIsEmpty(prhs[3])) numgrad(g[0],&badg[0],x[0]); else { for (i=0; i nit) { mexWarnMsgTxt("Iteration count termination"); done = true; } else if (stuck) { if (verbose==2) mexPrintf("improvement < crit termination\n"); done = true; } if (verbose) switch ((int)*retcodeh) { case 1: mexWarnMsgTxt("Zero gradient"); break; case 2: mexWarnMsgTxt("Back adjustment of stepsize didn't finish"); break; case 3: mexWarnMsgTxt("Smallest stepsize still improving too slow"); break; case 4: mexWarnMsgTxt("Forth adjustment of stepsize didn't finish"); break; case 6: mexWarnMsgTxt("Smallest step still improving too slow, reversed gradient"); break; case 5: mexWarnMsgTxt("Largest stepsize still improving too fast"); break; case 7: mexWarnMsgTxt("Possible inaccuracy in Hessian matrix"); break; } f[0] = *fh; memcpy(x[0],xh,n*sizeof(double)); memcpy(g[0],gh,n*sizeof(double)); badg[0] = badgh; } mxSetPr(fcnRhs[0],x[0]); /* added to prevent destroying array other than x[0] */ mxDestroyArray(fcnRhs[0]); mxFree(fcnRhs); for (i=0; i<4; i++) mxFree(g[i]); for (i=1; i<4; i++) mxFree(x[i]); } void numgrad(double *g, bool *badg, double *x) { static double delta=SCALE*1e-6, deltai=1e6/SCALE, f0, g0, tmp, *xp; register int i; f0 = feval(x); *badg = false; for (i=0, xp=x; i 1e12) { mexPrintf("Near-singular H problem.\n"); for (i=0; i -THETA*dfhat*lambda (see Berndt et al.) */ /* If that's not the case and grad is good, OR if grad is bad and f is not going down, shrinkSignal = true */ shrinkSignal = ( !badg && (f0-f <= (tmp>0?tmp:0)) ) || ( badg && (f0-f < 0 ) ); /* the optimal lambda should also be such that f0-f<-(1-THETA)*dfhat*lambda */ /* If that's not the case with lambda>0, AND grad is good, growthSignal = 1 */ growSignal = !badg && (lambda > 0) && (f0-f >= -(1-THETA)*dfhat*lambda); if (verbose==2) mexPrintf("\tS-G = %d-%d",shrinkSignal,growSignal); /* If shrinkSignal=true AND ( lambda>lambdaPeak or lambda negative ) */ /* (note when lambdaPeak=0 the second part only excludes lambda=0) */ /* try shrinking lambda */ if ( shrinkSignal && ( (lambda>lambdaPeak) || (lambda<0) ) ) { /* if shrink=false OR lambda/factor is already smaller than lambdaPeak, increase factor */ if ( (lambda>0) && ((!shrink) || (lambda/factor <= lambdaPeak)) ) { shrink = true; factor = pow(factor,.6); while (lambda/factor <= lambdaPeak) factor = pow(factor,.6); if (fabs(factor-1)lambdaPeak)) lambdaMax=lambda; /* shrink lambda */ lambda /= factor; /* if lambda has already been shrunk as much as possible */ if (fabs(lambda) < MINLAMB) /* if lambda is positive AND you have not made any improvement */ /* try going against gradient, which may be inaccurate */ if ((lambda > 0) && (f0 <= *fhat)) lambda = -lambda*pow(factor,6); else { /* if lambda is negative: let it be known and quit trying */ if (lambda < 0) *retcode = 6; /* if you have not made any imporvement: */ /* let it be known and quit trying */ else *retcode = 3; done = true; } } /* If growSignal=true and lambda positive OR ( lambda>lambdaPeak or lambda negative ) */ /* (note when lambdaPeak=0 the second part only excludes lambda=0) */ /* try increase lambda */ else if ( (growSignal && (lambda > 0) ) || ( shrinkSignal && (lambda <= lambdaPeak) && (lambda > 0) ) ) { if (shrink) { shrink = false; factor = pow(factor,.6); if (fabs(factor-1) < MINDFAC) { if (fabs(lambda) < 4) *retcode = 4; else *retcode = 7; done = true; } } if ( (f0) ) { fPeak = f; lambdaPeak = lambda; if (lambdaMax <= lambdaPeak) lambdaMax = lambdaPeak*factor*factor; } /* increase lambda (up to 1e20) */ lambda *= factor; /* if lambda has been increased up to the limit and */ /* you have not made any imporvement: */ /* let it be known and quit trying */ if (fabs(lambda) > 1e20) { *retcode = 5; done = true; } } /* If growthSignal=shrinkSignal=false you found a good lambda, you are done */ else { done = true; *retcode = factor<1.2 ? 7 : 0; } if (verbose==2) mexPrintf("\tfactor = %f\n",factor); } mxFree(dxtest); mxFree(dx); } if (verbose==2) mexPrintf("Norm of dx %10.5g; retcode = %d\n", dxnorm, *retcode); } double times(const double *x, const double *y) { double z = 0; register int i; for (i=0; i