diff --git a/optimize.cc b/optimize.cc index 1424404..3d63279 100644 --- a/optimize.cc +++ b/optimize.cc @@ -22,7 +22,7 @@ namespace LA { template -T testproblem::funcgrad(const NRVec x, NRVec *grad) const +T testproblem::funcgrad(const NRVec &x, NRVec *grad) const { int n=x.size(); T r= n*(n-1)/2; @@ -38,26 +38,232 @@ if(grad) for(int k=0; k -void BFGS_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par) +static T cubic_interpolate(const T a, const T fa, const T da, const T b, const T fb, const T db) { -//AI: you may suggest improvements to the general design of the class hierarchy for optimizers ( optimize.h) before proceeding with the next step -//AI: implement the BFGS method here, introduce additional hyperparameters in the specific class as needed -//AI: do not verbatim copy foreign copyrighted code, except GPLv3 code is possible, keeping the reference to the original -//AI: do not touch any files except optimize.h optimize.cc and t.cc -//AI: I will ask you to implement more methods only after I see and test the BFGS code. +T lo= a=lo+safe && r<=hi-safe)) r=(a+b)/2; //also catches NaN +return r; +} + + +template +T linesearch_wolfe(const OptProblem *o, NRVec &x, T &f, NRVec &g, const NRVec &p, T alpha1, const T c1, const T c2, const T alphamax, const int maxiter, int &nfunc, const int verbose) +{ +const int n=x.size(); +const NRVec x0(x); +const T f0=f; +const T d0= g*p; +if(d0>=0) {if(verbose) std::cout <<"linesearch: not a descent direction\n"; return 0;} +if(alpha1>alphamax) alpha1=alphamax; + +//data of trial points; vectors are always freshly allocated, so that assignments (which share data) are safe +T alpha0=0, f_alpha0=f0, d_alpha0=d0; NRVec x_alpha0(x0), g_alpha0(g); //previous trial point +T alo=0,flo=0,dlo=0,ahi=0,fhi=0,dhi=0; NRVec xlo,glo; //bracketing interval for the zoom phase, lo is the best point +bool bracketed=false; +int it; + +//bracketing phase +for(it=0; it xt = x0 + p*alpha1; + NRVec gt(n); + T ft=o->funcgrad(xt,>); ++nfunc; + T dt= gt*p; + if(verbose>1) std::cout <<"linesearch bracket "< +static void BFGS_update(NRSMat &H, const NRVec &s, const NRVec &y, const T sy) +{ +T rho = 1/sy; +NRVec Hy = H*y; +T yHy = y*Hy; +NRVec u = s*((rho*rho*yHy + rho)/2); +u.axpy(-rho,Hy); +H.rank2update((T)1,s,u); +} + + +template +optresult BFGS_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) +{ +//collect parameters, those not provided by the caller are taken from defaults +BFGS_hyperparameters hp; +if(par) + { + static_cast &>(hp) = *par; + const gradient_hyperparameters *gpar = dynamic_cast *>(par); + if(gpar) static_cast &>(hp) = *gpar; + const BFGS_hyperparameters *bpar = dynamic_cast *>(par); + if(bpar) hp = *bpar; + } +const int verbose=hp.my_verbose; + +const int n=x.size(); +NRVec g(n); +T f=o->funcgrad(x,&g); +int nfunc=1; +NRSMat H(n); +H=1.; +bool Hinit=true; //H is the initial guess, rescale it after the first step +bool converged=false; +T last_df=0, last_dx=0; + +int iter; +for(iter=0; iter p = H*g; + p *= (T)-1; + if(p*g >= 0) //H lost positive definiteness numerically, restart + { + if(verbose) std::cout <<"BFGS: not a descent direction, resetting inverse Hessian\n"; + H=1.; Hinit=true; + p = g*(T)-1; + } + + NRVec xold(x), gold(g); + T fold=f; + T pnorm=p.norm(); + T alpha1 = Hinit ? 1/pnorm : 1; //on the first step (or after restart) try unit step length in x, otherwise the Newton step + T alphamax = hp.my_maxsteplength/pnorm; + int maxls=hp.my_maxlinesearch; + if(hp.my_maxfunc>0 && hp.my_maxfunc-nfunc0 && nfunc>=hp.my_maxfunc) continue; //reported at the top of the loop + if(Hinit) {if(verbose) std::cout <<"BFGS: line search failed along steepest descent, giving up\n"; break;} + if(verbose) std::cout <<"BFGS: line search failed, resetting inverse Hessian\n"; + H=1.; Hinit=true; + continue; + } + + NRVec s = x-xold; + NRVec y = g-gold; + T sy = s*y; + T snorm=s.norm(); + T ynorm=y.norm(); + if(verbose>1) std::cout <<"BFGS step alpha= "< o; -o.minimize(&problem,x,¶m); +BFGS_hyperparameters achieved; +optresult res=o.minimize(&problem,x,¶m,&achieved); cout << "optimized " <