/* LA: linear algebra C++ interface library Copyright (C) 2026 Jiri Pittner or 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 3 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. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "optimize.h" #include "la_random.h" #include "diis.h" #include "nonclass.h" namespace LA { template void FrozenOptProblem::setfrozen(const bitvector &frozen) { const int n=xfull.size(); if((int)frozen.size()!=n) laerror("size mismatch in FrozenOptProblem::setfrozen"); NRVec index(n-(int)frozen.population()); for(int i=0, k=0; i T FrozenOptProblem::funcgrad(const NRVec &x, NRVec *grad) const { NRVec full=expand(x); if(!grad) return this->p->funcgrad(full,NULL); NRVec gfull(full.size()); T f=this->p->funcgrad(full,&gfull); grad->subvector_from(freeindex,gfull); return f; } //the capabilities are used only if the wrapped problem has them, as ensured by optcapability() template void FrozenOptProblem::hessian(const NRVec &x, NRSMat &h) const { NRSMat hfull(xfull.size()); optcapability >(this->p)->hessian(expand(x),hfull); h=hfull.submatrix(freeindex); } template NRVec FrozenOptProblem::hessian_times(const NRVec &x, const NRVec &v) const { NRVec vfull((T)0,xfull.size()); v.storesubvector_to(freeindex,vfull); return optcapability >(this->p)->hessian_times(expand(x),vfull).subvector(freeindex); } template void FrozenOptProblem::metric(const NRVec &x, NRSMat &g) const { NRSMat gfull(xfull.size()); optcapability >(this->p)->metric(expand(x),gfull); g=gfull.submatrix(freeindex); } template int FrozenOptProblem::degree(const int i) const {return optcapability >(this->p)->degree(freeindex[i]);} template T FrozenOptProblem::period(const int i) const {return optcapability >(this->p)->period(freeindex[i]);} template int FrozenOptProblem::nconstraints() const {return optcapability >(this->p)->nconstraints();} template void FrozenOptProblem::constraints(const NRVec &x, NRVec &c) const {optcapability >(this->p)->constraints(expand(x),c);} //Jacobian of the residuals by forward differences, columns (r(x + h_j e_j) - r(x))/h_j, h_j = h max(1,|x_j|); resid(x,r) evaluates the residuals template static void forward_difference_jacobian(F resid, const NRVec &x, const NRVec &r, NRMat &J, const T h) { NRVec xs(x); for(int j=0; j rs(r.size()); resid(xs,rs); rs-=r; rs*= 1/hj; J.columnset(rs,j); xs.copyonwrite(); xs[j]=x[j]; } } template void OptLeastSquares::jacobian_fd(const NRVec &x, const NRVec &r, NRMat &J) const { forward_difference_jacobian([this](const NRVec &xx, NRVec &rr){this->residuals(xx,rr);},x,r,J,fd_step); } template T OptLeastSquares::funcgrad(const NRVec &x, NRVec *grad) const { NRVec r(this->nresiduals()); this->residuals(x,r); if(grad) { NRMat J(r.size(),x.size()); if(!this->jacobian(x,J)) jacobian_fd(x,r,J); *grad = r*J; //J^T r } return (r*r)/2; } //central difference (f(x+h e_i) - f(x-h e_i))/(2h); xs is a working copy of x, restored on return template static T central_difference(const OptProblem *p, NRVec &xs, const NRVec &x, const int i, const T h) { xs[i]=x[i]+h; T d=p->funcgrad(xs,NULL); xs[i]=x[i]-h; d-=p->funcgrad(xs,NULL); xs[i]=x[i]; return d/(2*h); } template ParameterShift::ParameterShift(const OptProblem *pp, const T hh) : OptProblemAdapter(pp), trig(optcapability >(pp)), h(hh) { if(!trig) laerror("ParameterShift needs a problem providing OptTrigonometric"); } //derivative of a trigonometric polynomial of degree R with frequencies 1..R (in units of w): //f'(0) = sum_{mu=1}^{2R} (-1)^{mu-1} f(s_mu)/(4R sin^2(s_mu/2)), s_mu = (2mu-1)pi/(2R) (Wierichs et al., Quantum 6, 677 (2022)) template T ParameterShift::funcgrad(const NRVec &x, NRVec *grad) const { T f=this->p->funcgrad(x,NULL); if(!grad) return f; grad->copyonwrite(); NRVec xs(x); xs.copyonwrite(); //working copy with shifted elements for(int i=0; idegree(i); if(R==0) {(*grad)[i]=central_difference(this->p,xs,x,i,h); continue;} const T w=2*M_PI/trig->period(i); T d=0; for(int mu=1; mu<=2*R; ++mu) { const T s=(2*mu-1)*M_PI/(2*R); const T sn=std::sin(s/2); xs[i]=x[i]+s/w; d+= (mu%2 ? 1 : -1)*this->p->funcgrad(xs,NULL)/(4*R*sn*sn); } xs[i]=x[i]; (*grad)[i]=w*d; } return f; } template T FiniteDifference::funcgrad(const NRVec &x, NRVec *grad) const { T f=this->p->funcgrad(x,NULL); if(!grad) return f; grad->copyonwrite(); NRVec xs(x); xs.copyonwrite(); //working copy with shifted elements for(int i=0; ip,xs,x,i,h); return f; } //safeguarded cubic interpolation of the minimizer of phi between a and b, given function values and derivatives at both ends template static T cubic_interpolate(const T a, const T fa, const T da, const T b, const T fb, const T db) { T lo= a=lo+safe && r<=hi-safe)) r=(a+b)/2; //also catches NaN return r; } template T linesearch_wolfe(optevaluator &e, NRVec &x, T &f, NRVec &g, const NRVec &p, const T alpha1, const linesearch_hyperparameters &lp) { const T c1=lp.my_c1; const T c2=lp.my_c2; const int verbose=lp.my_verbose; const int maxiter= std::min(lp.my_maxlinesearch,e.remaining()); 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;} const T alphamax=lp.my_maxsteplength/p.norm(); T alpha=std::min(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*alpha; NRVec gt(n); T ft=e(xt,>); T dt= gt*p; if(verbose>1) std::cout <<"linesearch bracket "< static bool QN_update_H(NRSMat &H, bool &Hinit, const NRVec &s, const NRVec &y, const BFGS_hyperparameters &hp) { const T sy = s*y; const T snorm=s.norm(); const T ynorm=y.norm(); const bool curvature = sy > hp.my_sy_thr*snorm*ynorm; if(Hinit && curvature) {H = (T)(sy/(ynorm*ynorm)); Hinit=false;} switch(hp.my_update) { case QN_BFGS: //H+ = (I - rho s y^T) H (I - rho y s^T) + rho s s^T = H + s u^T + u s^T, rho=1/(y^T s), u = (rho^2 y^T H y + rho)/2 s - rho Hy { if(!curvature) return false; T rho = 1/sy; NRVec Hy = H*y; NRVec u = s*((rho*rho*(y*Hy) + rho)/2); u.axpy(-rho,Hy); H.rank2update((T)1,s,u); } break; case QN_DFP: //H+ = H - Hy (Hy)^T/(y^T H y) + s s^T/(y^T s) { if(!curvature) return false; NRVec Hy = H*y; T yHy = y*Hy; if(yHy<=0) return false; H.rank2update(1/(2*sy),s,s); H.rank2update(-1/(2*yHy),Hy,Hy); } break; case QN_SR1: //H+ = H + w w^T/(w^T y), w = s - Hy; H may become indefinite { NRVec w = H*y; w *= (T)-1; w += s; T wy = w*y; if(std::abs(wy) <= hp.my_sr1_thr*w.norm()*ynorm) return false; H.rank2update(1/(2*wy),w,w); } break; default: laerror("unknown quasi-Newton update"); } Hinit=false; return true; } template bool optstate::stop(const T ff, const T gn, const int needed) { f=ff; gnorm=gn; if(hp.my_verbose) { std::cout < optresult optstate::finish(hyperparameters *achieved) const { if(hp.my_verbose) { std::cout <::epsilon()*std::abs(t1)); //nonzero to guarantee termination while(!phi.exhausted()) { T mid=(lo+hi)/2; T tol1=tol*std::abs(xm)+tolabs; T tol2=2*tol1; if(std::abs(xm-mid) <= tol2-(hi-lo)/2) break; //converged bool goldenstep=true; if(std::abs(dprev)>tol1) //try parabolic interpolation through xm, w, v { T r=(xm-w)*(fx-fv); T q=(xm-v)*(fx-fw); T p=(xm-v)*q-(xm-w)*r; q=2*(q-r); if(q>0) p= -p; else q= -q; if(std::abs(p)q*(lo-xm) && pxm ? tol1 : -tol1; goldenstep=false; } } if(goldenstep) {dprev= xm>=mid ? lo-xm : hi-xm; d=cgold*dprev;} T t= std::abs(d)>=tol1 ? xm+d : xm+(d>0 ? tol1 : -tol1); T ft=phi(t); if(ft<=fx) { if(t>=xm) lo=xm; else hi=xm; v=w; fv=fw; w=xm; fw=fx; xm=t; fx=ft; } else { if(t1) std::cout <<"linemin t= "< optresult linesearch_minimize(const char *name, linesearch_direction &dir, const OptProblem *o, NRVec &x, const linesearch_hyperparameters &hp, hyperparameters *achieved) { const int verbose=hp.my_verbose; optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); const int n=x.size(); NRVec g(n); T f=e(x,&g); T alpha=0, dphi=0; //of the previous step for(;; ++st.iter) { if(st.stop(f,g.norm())) break; NRVec p = dir.direction(g); bool sd=dir.steepest(); //steepest descent step if(p*g >= 0) { if(dir.indefinite()) {if(verbose) std::cout <0) alpha1=alpha*dphi/dphi_new; else alpha1= sd ? 1/p.norm() : (T)1; dphi=dphi_new; NRVec xold(x), gold(g); T fold=f; alpha=linesearch_wolfe(e,x,f,g,p,alpha1,hp); if(alpha==0) { if(e.exhausted()) continue; //reported by stop() if(sd) {if(verbose) std::cout < s = x-xold; NRVec y = g-gold; if(verbose>1) std::cout < class QN_direction : public linesearch_direction { public: const BFGS_hyperparameters &hp; NRSMat H; bool Hinit; //H is the initial guess, rescale it after the first step QN_direction(const BFGS_hyperparameters &h, const int n) : hp(h), H(n), Hinit(true) {H=1.;}; NRVec direction(const NRVec &g) {NRVec p = H*g; p *= (T)-1; return p;} bool update(const NRVec &s, const NRVec &y, const NRVec &p, const NRVec &g) {return QN_update_H(H,Hinit,s,y,hp);} void reset() {H=1.; Hinit=true;} bool steepest() const {return Hinit;} bool indefinite() const {return hp.my_update==QN_SR1;} }; template optresult BFGS_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { static const char *names[]={"BFGS","DFP","SR1"}; BFGS_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("quasi-Newton with line search cannot search for saddle points"); QN_direction dir(hp,x.size()); return linesearch_minimize(names[hp.my_update],dir,o,x,hp,achieved); } template optresult GDIIS_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { const char *name="GDIIS"; GDIIS_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("GDIIS cannot search for saddle points"); if(hp.my_diis_dim<1) laerror("GDIIS needs diis_dim >= 1"); const int verbose=hp.my_verbose; optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); const int n=x.size(); QN_direction dir(hp,n); DIIS,NRVec > diis(hp.my_diis_dim); bool fresh=true; //first step after a (re)start, the quasi-Newton step from the initial Hessian guess has no scale NRVec g(n); T f=e(x,&g); for(;; ++st.iter) { if(st.stop(f,g.norm())) break; NRVec p = dir.direction(g); //quasi-Newton step, the error vector of DIIS if(p*g >= 0) { if(verbose) std::cout < xold(x), gold(g); T fold=f; NRVec s; bool accepted=false; if(!fresh) //DIIS extrapolation, otherwise a line search is done directly { s = x+p; NRVec err(p); diis.extrapolate(s,err,verbose>1,hp.my_diis_eps); s -= x; if(s*g >= 0 || s.norm() > hp.my_maxsteplength) { if(verbose) std::cout < hp.my_maxsteplength) s *= hp.my_maxsteplength/snorm; } NRVec xnew = x+s; NRVec gnew(n); T fnew=e(xnew,&gnew); if(fnew y = g-gold; if(verbose>1) std::cout < class LBFGS_direction : public linesearch_direction { public: const LBFGS_hyperparameters &hp; int m; //capacity int count; //no. of pairs stored int newest; //index of the newest pair NRVec > S,Y; NRVec rho; //1/(s.y) T gamma; //initial inverse Hessian H0 = gamma I, gamma = s.y/y.y of the newest pair (Nocedal & Wright eq. 7.20) LBFGS_direction(const LBFGS_hyperparameters &h) : hp(h), m(h.my_memory), count(0), newest(-1), S(m), Y(m), rho(m), gamma(1) {}; void push(const NRVec &s, const NRVec &y, const T sy) {newest=(newest+1)%m; S[newest]=s; Y[newest]=y; rho[newest]=1/sy; gamma=sy/(y*y); if(count direction(const NRVec &g); bool update(const NRVec &s, const NRVec &y, const NRVec &p, const NRVec &g) { T sy=s*y; if(sy <= hp.my_sy_thr*s.norm()*y.norm()) return false; //curvature condition violated numerically push(s,y,sy); return true; } void reset() {count=0; newest= -1; gamma=1;} bool steepest() const {return count==0;} }; //returns -H g by the two-loop recursion (Nocedal & Wright alg. 7.4) template NRVec LBFGS_direction::direction(const NRVec &g) { NRVec q(g); NRVec alpha(count); int k=newest; for(int i=0; i=0; --i) //oldest to newest { T beta = rho[k]*(Y[k]*q); q.axpy(alpha[i]-beta,S[k]); k=(k+1)%m; } q *= (T)-1; return q; } template optresult LBFGS_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { LBFGS_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("L-BFGS cannot search for saddle points"); if(hp.my_memory<1) laerror("L-BFGS needs memory >= 1"); LBFGS_direction dir(hp); return linesearch_minimize("L-BFGS",dir,o,x,hp,achieved); } //nonlinear CG: p_{k+1} = -g_{k+1} + beta p_k template class CG_direction : public linesearch_direction { public: const CG_hyperparameters &hp; int restart_interval; int count; //no. of steps since the last restart bool restart; //next direction is steepest descent T beta; T gg; //g.g of the gradient of the last direction NRVec d; //last search direction CG_direction(const CG_hyperparameters &h, const int n) : hp(h), restart_interval(h.my_restart>0 ? h.my_restart : n), count(0), restart(true), beta(0), gg(0) {}; NRVec direction(const NRVec &g) {NRVec p = g*(T)-1; if(!restart) p.axpy(beta,d); gg=g*g; return p;} bool update(const NRVec &s, const NRVec &y, const NRVec &p, const NRVec &g); void reset() {restart=true; count=0;} bool steepest() const {return restart;} bool alpha_from_previous() const {return true;} }; template bool CG_direction::update(const NRVec &s, const NRVec &y, const NRVec &p, const NRVec &g) { d=p; T gg1=g*g; T gy=g*y; T dy=p*y; restart= ++count>=restart_interval || (hp.my_powell_thr>0 && std::abs(gg1-gy) >= hp.my_powell_thr*gg1); //g_{k+1}.g_k = gg1-gy bool curvature= dy>0; if(!restart) switch(hp.my_beta) { case CG_FR: beta=gg1/gg; break; case CG_PR: beta=std::max((T)0,gy/gg); break; case CG_HS: if(curvature) beta=std::max((T)0,gy/dy); else restart=true; break; case CG_DY: if(curvature) beta=gg1/dy; else restart=true; break; case CG_HZ: if(curvature) beta=std::max((gy-2*(p*g)*(y*y)/dy)/dy, -1/(p.norm()*std::min((T)0.01,std::sqrt(gg)))); else restart=true; break; default: laerror("unknown CG beta formula"); } if(restart) { count=0; if(hp.my_verbose>1) std::cout <<"CG restart\n"; } return true; } template optresult CG_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { static const char *names[]={"CG-FR","CG-PR+","CG-HS+","CG-DY","CG-HZ"}; CG_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("CG cannot search for saddle points"); CG_direction dir(hp,x.size()); return linesearch_minimize(names[hp.my_beta],dir,o,x,hp,achieved); } template bool hessian_update(NRSMat &B, const NRVec &s, const NRVec &y, const TR_hessian type, const T thr) { const NRVec Bs=B*s; const T ss=s*s; NRVec xi(y); xi-=Bs; //y - Bs, vanishes if B already satisfies the secant condition const T xis=xi*s; switch(type) { case TR_BFGS: //B+ = B - Bs (Bs)^T/(s.Bs) + y y^T/(y.s) { const T sy=s*y, sBs=s*Bs; if(sy<=thr*std::sqrt(ss*(y*y)) || sBs<=0) return false; B.rank2update(1/(2*sy),y,y); B.rank2update(-1/(2*sBs),Bs,Bs); } break; case TR_SR1: //B+ = B + xi xi^T/(xi.s) (Nocedal & Wright eq. 6.24) if(std::abs(xis)<=thr*xi.norm()*std::sqrt(ss)) return false; B.rank2update(1/(2*xis),xi,xi); break; case TR_PSB: //B+ = B + (xi s^T + s xi^T)/(s.s) - (xi.s) s s^T/(s.s)^2 (Powell-symmetric-Broyden) B.rank2update(1/ss,xi,s); B.rank2update(-xis/(2*ss*ss),s,s); break; case TR_BOFILL: //phi*SR1 + (1-phi)*PSB, phi = (xi.s)^2/(|xi|^2 |s|^2), the SR1 term phi xi xi^T/(xi.s) = (xi.s) xi xi^T/(|xi|^2 |s|^2) has no small denominator { const T xixi=xi*xi; if(xixi==0) return false; const T phi=xis*xis/(xixi*ss); B.rank2update(xis/(2*xixi*ss),xi,xi); B.rank2update((1-phi)/ss,xi,s); B.rank2update(-(1-phi)*xis/(2*ss*ss),s,s); } break; default: laerror("unknown Hessian update"); } return true; } template optresult trustregion_minimize(const char *name, trustregion_step &stepper, const OptProblem *o, NRVec &x, const trustregion_hyperparameters &hp, hyperparameters *achieved) { const int verbose=hp.my_verbose; const bool saddle=stepper.saddle(); const OptHessian *hes=optcapability >(o); const bool exact= hp.my_hessian==TR_EXACT || (hp.my_hessian==TR_AUTO && hes); if(exact && !hes) laerror("exact Hessian requested, but the problem does not provide OptHessian"); TR_hessian update=hp.my_hessian; //update of B between exact Hessians or without them: Bofill for saddle points; for minima SR1 after if(update==TR_AUTO || update==TR_EXACT) update= saddle ? TR_BOFILL : (exact ? TR_SR1 : TR_BFGS); //an exact Hessian, which may be indefinite, BFGS from the identity if(saddle && update==TR_BFGS) laerror("the BFGS update keeps the Hessian positive definite, unsuitable for saddle points"); optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); const int n=x.size(); NRVec g(n); T f=e(x,&g); NRSMat B(n); bool Binit=false; //B is the unscaled initial guess if(exact) hes->hessian(x,B); else {B=1.; Binit=true;} int sinceexact=0; T delta=hp.my_delta0; for(;; ++st.iter) { if(st.stop(f,g.norm())) break; NRVec p=stepper.step(g,B,delta); const T pnorm=p.norm(); NRVec Bp=B*p; const T pred= g*p + 0.5*(p*Bp); //predicted change of f NRVec xn=x+p; NRVec gn(n); const T fn=e(xn,&gn); const T ratio= pred!=0 ? (fn-f)/pred : 0; if(verbose>1) std::cout < y=gn-g; const T fold=f; x=xn; f=fn; g=gn; if(exact && ++sinceexact>=hp.my_hessian_recompute) {hes->hessian(x,B); sinceexact=0;} else { if(Binit) //scaling of the initial guess (cf. Nocedal & Wright eq. 6.20) { const T sy=p*y; if(sy>0) B=(T)((y*y)/sy); Binit=false; } if(!hessian_update(B,p,y,update,hp.my_update_thr) && verbose>1) std::cout < H(n); if(hes) hes->hessian(x,H); else H=B; NRVec w(n); diagonalize(H,w,(NRMat *)NULL); int index=0; for(int i=0; i r=st.finish(achieved); if(achieved) achieved->my_saddle_order=index; return r; } //-a/(lambda+mu), for the components of a trust-region step in the eigenbasis of B template class shifteddivision { public: T mu; shifteddivision(const T m) : mu(m) {}; T operator()(const T &a, const T &lambda) const {return -a/(lambda+mu);} }; //exact solution of min g.p + 1/2 p.B.p, |p| <= delta in the eigenbasis of B = V Lambda V^T: p(mu) = -sum_i (v_i.g)/(lambda_i+mu) v_i //with mu = 0 for the Newton step inside the trust region, otherwise mu > -lambda_min from |p(mu)| = delta (More & Sorensen 1983) template static NRVec trustregion_eigenstep(const NRVec &g, const NRSMat &B, const T delta) { const int n=g.size(); NRSMat Bc(B); NRVec lam(n); NRMat V(n,n); diagonalize(Bc,lam,&V); //ascending eigenvalues, eigenvectors in the columns of V const NRVec gt=g*V; //components of g in the eigenbasis const T lmin=lam[0]; if(lmin>0) //Newton step if inside the trust region { NRVec pt(gt); pt.call_on_me(shifteddivision(0),lam); if(pt.norm()<=delta) return V*pt; } const T lo=std::max((T)0,-lmin); const T tol=1e-12*std::max(std::abs(lam[0]),std::abs(lam[n-1])); //hard case: g has no component along the eigenvectors of lambda_min and the step from the others at mu=-lambda_min is inside the trust region if(lmin<=0) { int nlow=0; while(nlow gtlow=gt.subvector(0,nlow-1); if(gtlow.norm()<=1e-12*g.norm()) { NRVec pt(gt); pt.copyonwrite(); for(int i=0; i p=V*pt; p.axpy(std::sqrt(delta*delta-pn*pn),V.column(0)); return p; } } } //|p(mu)| decreases from infinity at mu=lo; root by Newton's method on 1/|p(mu)| - 1/delta, safeguarded by bisection T mulo=lo, muhi=lo+g.norm()/delta; //|p(muhi)| <= delta T mu=muhi; NRVec pt(gt); for(int it=0; it<100; ++it) { pt=gt; pt.call_on_me(shifteddivision(mu),lam); const T pn=pt.norm(); if(std::abs(pn-delta)<=1e-10*delta) break; if(pn>delta) mulo=mu; else muhi=mu; NRVec qt(pt); //|q|^2 = sum_i pt_i^2/(lambda_i+mu) = -d|p|^2/dmu /2 qt.call_on_me(shifteddivision(mu),lam); const T qq= -(pt*qt); T munew= qq>0 ? mu+(pn*pn/qq)*(pn-delta)/delta : mu; if(!(munew>mulo && munew static NRVec trustregion_steihaug(const NRVec &g, const NRSMat &B, const T delta, int maxit) { const int n=g.size(); if(maxit<=0) maxit=n; NRVec z((T)0,n); const T gnorm=g.norm(); if(gnorm==0) return z; const T eps=std::min((T)0.5,std::sqrt(gnorm))*gnorm; NRVec r(g); NRVec d=g*(T)-1; for(int j=0; j Bd=B*d; const T dBd=d*Bd; const T rr=r*r; NRVec z1(z); if(dBd>0) z1.axpy(rr/dBd,d); if(dBd<=0 || z1.norm()>=delta) //negative curvature or outside: to the boundary along d, |z + tau d| = delta { const T a=d*d, b=2*(z*d), c=z*z-delta*delta; z.axpy((-b+std::sqrt(b*b-4*a*c))/(2*a),d); return z; } r.axpy(rr/dBd,Bd); z=z1; if(r.norm() class TR_newtonstep : public trustregion_step { public: const TrustRegion_hyperparameters &hp; TR_newtonstep(const TrustRegion_hyperparameters &h) : hp(h) {}; NRVec step(const NRVec &g, const NRSMat &B, const T delta) {return hp.my_subproblem==TR_STEIHAUG ? trustregion_steihaug(g,B,delta,hp.my_maxcg) : trustregion_eigenstep(g,B,delta);} }; template optresult TrustRegion_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { TrustRegion_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("TrustRegion_Optimizer minimizes, use PRFO_Optimizer for saddle points"); TR_newtonstep stepper(hp); return trustregion_minimize(hp.my_subproblem==TR_STEIHAUG ? "TR-Steihaug" : "TR-Newton",stepper,o,x,hp,achieved); } //shift of the RFO step for the modes from..to-1: largest (maximization) or lowest (minimization) eigenvalue of the bordered matrix [[Lambda, gt],[gt^T, 0]] template static T rfo_shift(const NRVec &lam, const NRVec >, const int from, const int to, const bool largest) { const int m=to-from; NRSMat A(m+1); A=(T)0; for(int i=0; i w(m+1); diagonalize(A,w,(NRMat *)NULL); return largest ? w[m] : w[0]; } //P-RFO step: in the eigenbasis of B, p_i = -gt_i/(lambda_i - mu) with mu from the RFO matrices of the k maximized and of the minimized modes, //scaled to the trust region if longer template class PRFO_step : public trustregion_step { public: int k; //no. of maximized modes PRFO_step(const int kk) : k(kk) {}; bool saddle() const {return k>0;} NRVec step(const NRVec &g, const NRSMat &B, const T delta) { const int n=g.size(); NRSMat Bc(B); NRVec lam(n); NRMat V(n,n); diagonalize(Bc,lam,&V); const NRVec gt=g*V; const T mumax= k>0 ? rfo_shift(lam,gt,0,k,true) : 0; const T mumin= k pt(n); for(int i=0; i p=V*pt; const T pn=p.norm(); if(pn>delta) p*= delta/pn; return p; } }; template optresult PRFO_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { PRFO_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order<0 || hp.my_saddle_order>x.size()) laerror("invalid saddle order in PRFO_Optimizer"); PRFO_step stepper(hp.my_saddle_order); return trustregion_minimize("P-RFO",stepper,o,x,hp,achieved); } //Jacobian for LM: from the problem, or by forward differences with counted evaluations; false if the evaluation budget is insufficient template static bool lm_jacobian(optevaluator &e, const OptResiduals *res, const NRVec &x, const NRVec &r, NRMat &J, const T h) { if(res->jacobian(x,J)) return true; if(e.remaining() &xx, NRVec &rr){e(res,xx,rr);},x,r,J,h); return true; } template optresult LM_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { const char *name="Levenberg-Marquardt"; LM_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("Levenberg-Marquardt cannot search for saddle points"); const OptResiduals *res=optcapability >(o); if(!res) laerror("Levenberg-Marquardt needs a least-squares problem providing OptResiduals"); const int verbose=hp.my_verbose; const int n=x.size(), m=res->nresiduals(); optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); NRVec r(m); T f=e(res,x,r); NRMat J(m,n); const bool fdjac= !res->jacobian(x,J); //the problem does not provide the Jacobian bool budget= !fdjac || lm_jacobian(e,res,x,r,J,hp.my_fd_step); NRSMat A=J.transposedtimes(); //J^T J NRVec g=r*J; //J^T r, the gradient of f NRVec diag(n); for(int i=0; i &)A)(i,i); T mu=hp.my_tau*diag.amax(), nu=2; for(; budget; ++st.iter) { if(st.stop(f,g.norm(),fdjac ? n+1 : 1)) break; //damped step (J^T J + mu D) h = -J^T r NRSMat M(A); M.copyonwrite(); for(int i=0; i h=g*(T)-1; linear_solve(M,h); NRVec xn=x+h; NRVec rn(m); const T fn=e(res,xn,rn); NRVec Dh(h); if(hp.my_scaling) Dh*=diag; Dh*=mu; Dh-=g; const T pred=(h*Dh)/2; //predicted reduction L(0)-L(h) of the linear model const T rho=(f-fn)/pred; if(verbose>1) std::cout < class NM_simplex { public: int n; NRVec > v; //vertices NRVec f; //function values NRVec sum; //sum of the vertices int best, second, worst; //indices of the lowest, second highest and highest function value NM_simplex(const NRVec &x0, const T h, optevaluator &e); void order(); void sumup() {sum=v[0]; for(int i=1; i<=n; ++i) sum+=v[i];} void replace(const int i, const NRVec &x, const T fx) {sum-=v[i]; sum+=x; v[i]=x; f[i]=fx;} NRVec centroid() const {NRVec c(sum); c-=v[worst]; c/=(T)n; return c;} //of all vertices except the worst one NRVec point(const NRVec &c, const T t) const {NRVec x = c*(1+t); x.axpy(-t,v[worst]); return x;} //c + t*(c - x_worst) T diameter() const; void shrink(const T sigma, optevaluator &e); }; //initial simplex x0, x0 + h e_i template NM_simplex::NM_simplex(const NRVec &x0, const T h, optevaluator &e) : n(x0.size()), v(n+1), f(n+1), best(0), second(0), worst(0) { v[0]=x0; f[0]=e(x0); for(int i=1; i<=n; ++i) { v[i]=x0; v[i].copyonwrite(); v[i][i-1] += h; f[i]=e(v[i]); } sumup(); } template void NM_simplex::order() { best=worst=0; for(int i=1; i<=n; ++i) { if(f[i]=f[worst]) worst=i; } second=best; for(int i=0; i<=n; ++i) if(i!=worst && f[i]>=f[second]) second=i; } template T NM_simplex::diameter() const { T d=0; for(int i=0; i<=n; ++i) if(i!=best) { NRVec dx = v[i]-v[best]; d=std::max(d,dx.norm()); } return d; } //all vertices move towards the best one, as far as the evaluation budget allows template void NM_simplex::shrink(const T sigma, optevaluator &e) { for(int i=0; i<=n; ++i) if(i!=best) { if(e.exhausted()) break; NRVec x = v[best]*(1-sigma); x.axpy(sigma,v[i]); v[i]=x; f[i]=e(x); } sumup(); } template optresult NelderMead_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { const char *name="Nelder-Mead"; NelderMead_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("Nelder-Mead cannot search for saddle points"); const int verbose=hp.my_verbose; const int n=x.size(); T alpha=hp.my_alpha, gamma=hp.my_gamma, rho=hp.my_rho, sigma=hp.my_sigma; if(hp.my_adaptive) {alpha=1; gamma=1+(T)2/n; rho=0.75-(T)0.5/n; sigma=1-(T)1/n;} optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); NM_simplex s(x,hp.my_initstep,e); const T huge=std::numeric_limits::max(); for(;; ++st.iter) { s.order(); T spread=s.f[s.worst]-s.f[s.best]; st.step(spread, spread c=s.centroid(); NRVec xr=s.point(c,alpha); T fr=e(xr); if(fr xe=s.point(c,alpha*gamma); T fe=e(xe); if(fe xc=s.point(c, outside ? alpha*rho : -rho); T fc=e(xc); if(outside ? fc<=fr : fc1) std::cout < optresult Powell_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { const char *name="Powell"; Powell_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("Powell cannot search for saddle points"); const int verbose=hp.my_verbose; const T abstol=0.01*hp.my_x_thr; //line minima have to be more precise than the requested accuracy in x const int n=x.size(); optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); T f=e(x); NRVec > u(n); //normalized directions for(int i=0; i((T)0,n); u[i][i]=1;} NRVec tstep(hp.my_initstep,n); //trial steps of the line minimizations, from the last step along each direction for(;; ++st.iter) { if(st.stop(f)) break; NRVec x0(x); T f0=f; T delta=0; //largest decrease along a single direction int ibig=0; for(int i=0; idelta) {delta=fprev-f; ibig=i;} } //new direction x-x0 replaces the one of the largest decrease, if Powell's test indicates it is worth NRVec unew = x-x0; T unorm=unew.norm(); if(unorm>0 && !e.exhausted()) { NRVec xe = x+unew; //extrapolated point T fe=e(xe); T a=f0-f-delta; T b=f0-fe; if(fe1) std::cout < dx = x-x0; st.step(f0-f,dx.norm()); } return st.finish(achieved); } //natural gradient (G + reg I)^-1 g from the gradient of an inner estimator and the metric G of the problem template class natural_gradient_estimator : public gradient_estimator { public: gradient_estimator &inner; const OptMetric *m; T reg; natural_gradient_estimator(gradient_estimator &in, const OptMetric *mm, const T r) : inner(in), m(mm), reg(r) {}; T operator()(optevaluator &e, const NRVec &x, NRVec &g, const int k) { T f=inner(e,x,g,k); NRSMat G(x.size()); m->metric(x,G); if(reg!=0) G+=reg; linear_solve(G,g); return f; } int cost() const {return inner.cost();} bool exact() const {return inner.exact();} T calibrate(optevaluator &e, const NRVec &x, const firstorder_hyperparameters &hp) {return inner.calibrate(e,x,hp);} }; template optresult firstorder_minimize(const char *name, firstorder_step &stepper, const OptProblem *o, NRVec &x, const firstorder_hyperparameters &hp, hyperparameters *achieved, gradient_estimator *est) { gradient_estimator exact; gradient_estimator *plain = est ? est : &exact; const OptMetric *metric=NULL; if(hp.my_natural) { metric=optcapability >(o); if(!metric) laerror("natural gradient needs a problem providing the metric (OptMetric)"); } natural_gradient_estimator natural(*plain,metric,hp.my_metric_reg); gradient_estimator &grad = hp.my_natural ? natural : *plain; optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); const int n=x.size(); const T lr0=grad.calibrate(e,x,hp); if(hp.my_verbose && lr0!=hp.my_lr) std::cout < g(n); T f=grad(e,x,g,0); for(;; ++st.iter) { if(st.stop(f,grad.exact() ? g.norm() : (T)-1,grad.cost())) break; T lr=lr0/std::pow(1+hp.my_lr_decay*st.iter,hp.my_lr_power); NRVec dx = stepper.step(g,st.iter+1); dx *= -lr; if(hp.my_weight_decay!=0) dx.axpy(-lr*hp.my_weight_decay,x); x += dx; T fold=f; NRVec gnew(n); //fresh vector, the stepper may keep the previous gradient f=grad(e,x,gnew,st.iter+1); g=gnew; if(grad.exact()) st.step(f-fold,dx.norm()); //for estimates, small changes are no sign of convergence } if(hp.my_return_best && e.fbest stepper(hp); return firstorder_minimize("QNG",stepper,o,x,hp,achieved); } //approximate solution of min c.z subject to A z >= r and |(z_0,...,z_{nd-1})| <= delta, nd = nz or nz-1, from a feasible z by an active-set method: //z moves along the projection of -c orthogonal to the normals of the active constraints until a further constraint becomes active, //the trust region boundary is reached (which finishes the calculation as in COBYLA), or the multipliers show optimality; //the projection is computed by least squares, which tolerates dependent normals at degenerate points template static void lp_trustregion(const NRVec &c, const NRMat &A, const NRVec &r, const int nd, const T delta, NRVec &z) { const int nz=c.size(); const int m=r.size(); const T tiny=1e3*std::numeric_limits::epsilon()*c.norm(); NRVec active(m); active.clear(); NRVec cols(nz); for(int j=0; j &zc=z; //element access for reading only const int maxit=2*(m+nz)+10; for(int it=0; it rows(k); k=0; for(int i=0; i s = c*(T)-1; //search direction NRVec lambda; //multipliers of the active constraints if(k>0) { NRMat N=A.submatrix(rows,cols); //normals of the active constraints NRMat Nt=N.transpose(); lambda=svd_solve(Nt,c); //least squares c ~ N^T lambda NRVec nl=lambda*N; s+=nl; } if(s.norm()<=tiny) //stationary on the active set, optimal if the multipliers are nonnegative { int drop= -1; T lmin=0; for(int j=0; j Az=A*z; NRVec As=A*s; T alpha=std::numeric_limits::max(); int block= -1; for(int i=0; i0) { T ab=(-zs+std::sqrt(std::max((T)0,zs*zs-ss*(zz-delta*delta))))/ss; if(ab= 0 within |d| <= rho; if they cannot be satisfied //in the trust region, the largest violation t is minimized first (stage 1) and then kept, c + A d >= -t (stage 2) (Powell 1994) template static NRVec cobyla_step(const NRVec &g, const NRMat &A, const NRVec &c, const T rho) { const int n=g.size(); const int m=c.size(); NRVec d((T)0,n); if(m==0) {T gn=g.norm(); if(gn>0) d.axpy(-rho/gn,g); return d;} T t=std::max((T)0,-c.min()); if(t>0) //stage 1 in the variables (d,t): min t subject to c_i + a_i.d + t >= 0, t >= 0 { NRMat A1(m+1,n+1); A1.clear(); A1.storesubmatrix(0,0,A); for(int i=0; i<=m; ++i) A1(i,n)=1; NRVec r1(m+1); //r1_i = -c_i, r1_m = 0 r1.clear(); for(int i=0; i c1((T)0,n+1); c1[n]=1; NRVec z((T)0,n+1); z[n]=t; lp_trustregion(c1,A1,r1,n,rho,z); t=z[n]; for(int j=0; j= -t NRVec r2 = c*(T)-1; r2-=t; lp_trustregion(g,A,r2,n,rho,d); return d; } template static T cobyla_violation(const NRVec &c) {return c.size()>0 ? std::max((T)0,-c.min()) : 0;} //f and the constraint values at x template static T cobyla_eval(optevaluator &e, const OptConstraints *con, const NRVec &x, NRVec &c) { T f=e(x); if(con) con->constraints(x,c); return f; } template optresult Cobyla_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { const char *name="COBYLA"; Cobyla_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("COBYLA cannot search for saddle points"); const T acc_sigma=0.25, acc_eta=2.1, geo_gamma=0.5; //simplex acceptability and geometry step (Powell 1994) const int verbose=hp.my_verbose; const int n=x.size(); const OptConstraints *con=optcapability >(o); const int m= con ? con->nconstraints() : 0; optevaluator e(o,hp.my_maxfunc); optstate st(name,hp,e); //initial simplex x, x + rhobeg e_j with function and constraint values T rho=hp.my_rhobeg; //resolution, lower bound of the trust region radius T delta=rho; //trust region radius, always equal to rho in Powell's original COBYLA const bool flexible=hp.my_flexible_radius; T mu=0; //penalty parameter of the merit function f + mu*(largest constraint violation) NRVec > v(n+1), cv(n+1); NRVec fv(n+1); for(int j=0; j<=n; ++j) { v[j]=x; if(j>0) {v[j].copyonwrite(); v[j][j-1]+=rho;} cv[j]=NRVec(m); fv[j]=cobyla_eval(e,con,v[j],cv[j]); } int best=0; T fprev=fv[0]; NRVec other(n); //vertices other than the best one bool lastsuccess=false; //the last trust region step was successful, the next one follows without checking the simplex for(;; ++st.iter) { best=0; for(int j=1; j<=n; ++j) if(fv[j]+mu*cobyla_violation(cv[j]) < fv[best]+mu*cobyla_violation(cv[best])) best=j; if(st.iter>0) {st.step(fprev-fv[best],rho); fprev=fv[best];} if(st.stop(fv[best])) break; for(int j=0, k=0; j<=n; ++j) if(j!=best) other[k++]=j; //displacements d_k of the other vertices from the best one and the dual basis B = D^-T, b_k.d_l = delta_kl NRMat D(n,n); for(int k=0; k dk=v[other[k]]-v[best]; D.rowset(dk,k);} NRMat B=D.inverse().transpose(); //linear models: gradient g of f, gradients of the constraints in the rows of A, values c0 at the best vertex NRVec df(n); for(int k=0; k g=df*B; const NRVec c0(cv[best]); NRMat A; if(m>0) { NRMat dC(n,m); for(int k=0; k dc=cv[other[k]]-c0; dC.rowset(dc,k);} A=dC.transpose()*B; } //acceptability of the simplex: distances eta_k = |d_k| <= acc_eta delta and sigma_k = 1/|b_k| >= acc_sigma rho of the vertices from the opposite faces int worst= -1; T etamax=acc_eta*delta; for(int k=0; ketamax) {etamax=eta; worst=k;}} if(worst<0) { T sigmin=acc_sigma*rho; for(int k=0; k=0) //geometry step: the worst vertex moves to the best one + a step along the normal of its opposite face, with the sign preferred by the merit model { NRVec dir=B.row(worst); //step length geo_gamma rho, or with flexible radius max(min(0.1 DIST, 0.5 delta), rho) (NEWUOA eq. (6.16)), DIST the distance of the replaced vertex T len= flexible ? std::max(std::min((T)0.1*D.row(worst).norm(),(T)0.5*delta),rho) : geo_gamma*rho; dir*= len/dir.norm(); T mplus=g*dir, mminus= -mplus; if(m>0) { NRVec ad=A*dir; NRVec cp(c0), cm(c0); cp+=ad; cm-=ad; mplus+=mu*cobyla_violation(cp); mminus+=mu*cobyla_violation(cm); } if(mminus1) std::cout < xn=v[best]+dir; NRVec cn(m); T fn=cobyla_eval(e,con,xn,cn); v[other[worst]]=xn; fv[other[worst]]=fn; cv[other[worst]]=cn; continue; } //trust region step lastsuccess=false; NRVec d=cobyla_step(g,A,c0,delta); const T dnorm=d.norm(); const T deltaold=delta; //trust region radius of this step T ratio= -1; //reduction ratio of the merit function const bool shortstep= dnorm<0.5*rho; bool failed=shortstep; if(shortstep && flexible) delta=std::max((T)0.1*delta,rho); //NEWUOA Box 15 if(!failed) { const T v0=cobyla_violation(c0); NRVec cd(c0); if(m>0) {NRVec ad=A*d; cd+=ad;} T fpred= -(g*d); //predicted reductions of f and of the violation T vpred=v0-cobyla_violation(cd); if(vpred>0 && mu < -1.5*fpred/vpred) //penalty has to make the predicted reduction of the merit function positive { mu= -2*fpred/vpred; if(verbose>1) std::cout < xn=v[best]+d; NRVec cn(m); T fn=cobyla_eval(e,con,xn,cn); T phi0=fv[best]+mu*v0; T phin=fn+mu*cobyla_violation(cn); ratio=(phi0-phin)/pred; if(verbose>1) std::cout < class trigpolynomial : public OptProblem { public: NRVec a, b; T w; trigpolynomial(const int R, const T ww) : a(R+1), b(R+1), w(ww) {}; T funcgrad(const NRVec &x, NRVec *grad) const { if(grad) laerror("trigpolynomial provides only the function"); T f=a[0]; for(int k=1; k static bool trig_minimize(optevaluator &e, NRVec &x, T &f, const int i, const int R, const T period, const bool reevaluate, const int verbose) { const int N=2*R+1; if(e.remaining() &)x)[i]; //const access, x may be shared with the evaluator NRVec fs(N); //samples fs[0]=f; NRVec xs(x); for(int j=1; j p(R,w); p.a[0]=fs.sum()/N; for(int k=1; k<=R; ++k) { p.a[k]=p.b[k]=0; for(int j=0; j tt(1); int jbest=0; T fbest=0; for(int j=0; j pe(&p); tt[0]=jbest*period/M; NRVec u((T)1,1); linemin_brent(pe,tt,fbest,u,period/M,(T)1e-10,(T)1e-12*period,100); t=tt[0]; fmin=fbest; } t-= period*std::floor(t/period+0.5); //shortest step, t in (-period/2,period/2] x.copyonwrite(); x[i]=xi+t; f= reevaluate ? e(x) : fmin; if(verbose>1) std::cout <<"trigonometric minimization of parameter "< hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("Rotosolve cannot search for saddle points"); const int verbose=hp.my_verbose; const T abstol=0.01*hp.my_x_thr; //as in Powell's method const int n=x.size(); const OptTrigonometric *trig=optcapability >(o); if(!trig && verbose) std::cout < e(o,hp.my_maxfunc); optstate st(name,hp,e); T f=e(x); NRVec tstep(hp.my_initstep,n); //trial steps of the line minimizations for(;; ++st.iter) { if(st.stop(f)) break; NRVec x0(x); T f0=f; bool budget=true; for(int i=0; idegree(i) : 0; if(R>0) budget=trig_minimize(e,x,f,i,R,trig->period(i),hp.my_reevaluate,verbose); else { NRVec u((T)0,n); u[i]=1; T t=linemin_brent(e,x,f,u,tstep[i],hp.my_linmin_tol,abstol,hp.my_maxlinemin,verbose); if(t!=0) tstep[i]=std::abs(t); budget=!e.exhausted(); } } NRVec dx = x-x0; st.step(f0-f,dx.norm()); if(!budget) //incomplete sweep, no convergence test { if(verbose) std::cout < optresult SGD_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { SGD_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("SGD cannot search for saddle points"); SGD_stepper stepper(hp); return firstorder_minimize(hp.my_nesterov ? "SGD-Nesterov" : "SGD",stepper,o,x,hp,achieved); } //elementwise functions for Adam template static T elementmax(const T &a, const T &b) {return a>b ? a : b;} //m/(sqrt(v)/sqrtc2 + eps) with the bias correction sqrtc2 = sqrt(1-beta2^k) template class adamratio { public: T sqrtc2, eps; adamratio(const T s, const T e) : sqrtc2(s), eps(e) {}; T operator()(const T &m, const T &v) const {return m/(std::sqrt(v)/sqrtc2+eps);} }; //Adam, AdamW, AMSGrad template class Adam_stepper : public firstorder_step { public: const Adam_hyperparameters &hp; NRVec m; //first moment estimate NRVec v; //second moment estimate NRVec vmax; //maximum of the second moments for AMSGrad T beta1k, beta2k; //beta1^k, beta2^k Adam_stepper(const Adam_hyperparameters &h) : hp(h), beta1k(1), beta2k(1) {}; NRVec step(const NRVec &g, const int k) { NRVec g2(g); g2 *= g; if(k==1) {m = g*(1-hp.my_beta1); v = g2*(1-hp.my_beta2);} else { m *= hp.my_beta1; m.axpy(1-hp.my_beta1,g); v *= hp.my_beta2; v.axpy(1-hp.my_beta2,g2); } beta1k *= hp.my_beta1; beta2k *= hp.my_beta2; if(hp.my_amsgrad) {if(k==1) vmax=v; else vmax.call_on_me(elementmax,v);} NRVec d = m*(1/(1-beta1k)); d.call_on_me(adamratio(std::sqrt(1-beta2k),hp.my_eps), hp.my_amsgrad ? vmax : v); return d; } }; template optresult Adam_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { Adam_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("Adam cannot search for saddle points"); Adam_stepper stepper(hp); return firstorder_minimize(hp.my_amsgrad ? "AMSGrad" : (hp.my_weight_decay!=0 ? "AdamW" : "Adam"),stepper,o,x,hp,achieved); } //SPSA gradient estimate (g_hat)_i = (f(x+c_k Delta) - f(x-c_k Delta))/(2 c_k Delta_i), Delta_i = +-1, averaged over resamplings template class SPSA_estimator : public gradient_estimator { public: const SPSA_hyperparameters &hp; SPSA_estimator(const SPSA_hyperparameters &h) : hp(h) {}; T operator()(optevaluator &e, const NRVec &x, NRVec &g, const int k); int cost() const {return 2*hp.my_resamplings;} bool exact() const {return false;} T calibrate(optevaluator &e, const NRVec &x, const firstorder_hyperparameters &h); }; //returns the average of f(x+c_k Delta) and f(x-c_k Delta) as an estimate of f(x) template T SPSA_estimator::operator()(optevaluator &e, const NRVec &x, NRVec &g, const int k) { const int n=x.size(); const T ck=hp.my_c/std::pow((T)(k+1),hp.my_gamma); g.clear(); T fsum=0; for(int r=0; r delta(n); //fresh vector, element access is safe for(int i=0; i xp(x), xm(x); xp.axpy(ck,delta); xm.axpy(-ck,delta); T fp=e(xp); T fm=e(xm); g.axpy((fp-fm)/(2*ck),delta); //1/Delta_i = Delta_i fsum+=fp+fm; } g /= (T)hp.my_resamplings; return fsum/(2*hp.my_resamplings); } //learning rate such that the first step lr*|g_hat| has on average the target length template T SPSA_estimator::calibrate(optevaluator &e, const NRVec &x, const firstorder_hyperparameters &h) { if(hp.my_calibrate<=0) return hp.my_lr; T gsum=0; int ncal=0; for(; ncal=2*cost(); ++ncal) //leave the budget for the first estimate { NRVec g(x.size()); (*this)(e,x,g,0); gsum+=g.norm(); } if(ncal==0 || gsum==0) return hp.my_lr; return hp.my_target_step*ncal/gsum; } //plain gradient step for SPSA template class plain_stepper : public firstorder_step { public: NRVec step(const NRVec &g, const int k) {return g;} }; template optresult SPSA_Optimizer::minimize(const OptProblem *o, NRVec &x, const hyperparameters *par, hyperparameters *achieved) { SPSA_hyperparameters hp; if(par) hp.setfrom(*par); if(hp.my_saddle_order) laerror("SPSA cannot search for saddle points"); if(hp.my_resamplings<1) laerror("SPSA needs resamplings >= 1"); plain_stepper stepper; SPSA_estimator est(hp); return firstorder_minimize("SPSA",stepper,o,x,hp,achieved,&est); } //force instantiation #define INSTANTIZE(T) \ template T linesearch_wolfe(optevaluator &e, NRVec &x, T &f, NRVec &g, const NRVec &p, const T alpha1, const linesearch_hyperparameters &lp); \ template class optstate; \ template class ParameterShift; \ template class FiniteDifference; \ template class FrozenOptProblem; \ template class BFGS_Optimizer; \ template class LBFGS_Optimizer; \ template class GDIIS_Optimizer; \ template class TrustRegion_Optimizer; \ template class PRFO_Optimizer; \ template class LM_Optimizer; \ template class OptLeastSquares; \ template bool hessian_update(NRSMat &B, const NRVec &s, const NRVec &y, const TR_hessian type, const T thr); \ template class CG_Optimizer; \ template class NelderMead_Optimizer; \ template class Powell_Optimizer; \ template class Rotosolve_Optimizer; \ template class Cobyla_Optimizer; \ template class SGD_Optimizer; \ template class QNG_Optimizer; \ template class Adam_Optimizer; \ template class SPSA_Optimizer; \ template T linemin_brent(optevaluator &e, NRVec &x, T &f, const NRVec &u, const T t1, const T tol, const T abstol, const int maxiter, const int verbose); \ INSTANTIZE(double) }//namespace