/* 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 T testproblem::funcgrad(const NRVec &x, NRVec *grad) const { int n=x.size(); T r= n*(n-1)/2; for(int i=0; isize()!=x.size()) laerror("grad size mismatch"); grad->copyonwrite(); //I forgot this initially for(int k=0; k 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; } //Bloch vector components z = cos a cos b, x = cos a sin b of the qubit RY(b) RX(a)|0> and their derivatives template class blochqubit { public: T z, x, za, zb, xa, xb; blochqubit(const T a, const T b) { T ca=std::cos(a), sa=std::sin(a), cb=std::cos(b), sb=std::sin(b); z=ca*cb; x=ca*sb; za= -sa*cb; zb= -ca*sb; xa= -sa*sb; xb=ca*cb; } }; template T testproblem_vqe::funcgrad(const NRVec &x, NRVec *grad) const { const int nq=x.size()/2; if(2*nq!=x.size()) laerror("testproblem_vqe needs an even number of parameters"); T r=0; for(int i=0; i q(x[2*i],x[2*i+1]); r+= hfield(i)*q.z + kfield(i)*q.x; if(isize()!=x.size()) laerror("grad size mismatch"); grad->copyonwrite(); for(int i=0; i q(x[2*i],x[2*i+1]); T eff=hfield(i); //coefficient of z_i including the couplings if(i>0) eff+= J(i-1)*std::cos(x[2*i-2])*std::cos(x[2*i-1]); if(i void testproblem_vqe::hessian(const NRVec &x, NRSMat &h) const { const int nq=x.size()/2; h.copyonwrite(); h=(T)0; for(int i=0; i q(x[2*i],x[2*i+1]); T eff=hfield(i); if(i>0) eff+= J(i-1)*std::cos(x[2*i-2])*std::cos(x[2*i-1]); if(i q1(x[2*i+2],x[2*i+3]); h(2*i,2*i+2)= J(i)*q.za*q1.za; h(2*i,2*i+3)= J(i)*q.za*q1.zb; h(2*i+1,2*i+2)= J(i)*q.zb*q1.za; h(2*i+1,2*i+3)= J(i)*q.zb*q1.zb; } } } template void testproblem_vqe::metric(const NRVec &x, NRSMat &g) const { const int nq=x.size()/2; g.copyonwrite(); g=(T)0; for(int i=0; i 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); } //simplex of n+1 vertices with their function values template 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); } //trigonometric polynomial a_0 + sum_{k=1}^R (a_k cos(k w t) + b_k sin(k w t)) of the single variable t = x[0] template 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 template double linesearch_wolfe(optevaluator &e, NRVec &x, double &f, NRVec &g, const NRVec &p, const double alpha1, const linesearch_hyperparameters &lp); template class optstate; template class testproblem; template class testproblem_vqe; template class ParameterShift; template class FiniteDifference; template class BFGS_Optimizer; template class LBFGS_Optimizer; template class GDIIS_Optimizer; template class CG_Optimizer; template class NelderMead_Optimizer; template class Powell_Optimizer; template class Rotosolve_Optimizer; template class SGD_Optimizer; template class QNG_Optimizer; template class Adam_Optimizer; template class SPSA_Optimizer; template double linemin_brent(optevaluator &e, NRVec &x, double &f, const NRVec &u, const double t1, const double tol, const double abstol, const int maxiter, const int verbose); }//namespace