/* 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" 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"); for(int k=0; k 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(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= "<