optimize: freezing parameters in a problem using a bitvector

This commit is contained in:
jiri committed 2026-10-26 16:31:36 +01:00
1 parent 0900678e5c
commit 3ef603c184
4 files changed
+196 -2

No files matched your search

+60
View File
@@ -24,6 +24,65 @@
namespace LA {
template<typename T>
void FrozenOptProblem<T>::setfrozen(const bitvector &frozen)
{
const int n=xfull.size();
if((int)frozen.size()!=n) laerror("size mismatch in FrozenOptProblem::setfrozen");
NRVec<int> index(n-(int)frozen.population());
for(int i=0, k=0; i<n; ++i) if(!frozen[i]) index[k++]=i;
freeindex=index;
}
template<typename T>
T FrozenOptProblem<T>::funcgrad(const NRVec<T> &x, NRVec<T> *grad) const
{
NRVec<T> full=expand(x);
if(!grad) return this->p->funcgrad(full,NULL);
NRVec<T> 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<typename T>
void FrozenOptProblem<T>::hessian(const NRVec<T> &x, NRSMat<T> &h) const
{
NRSMat<T> hfull(xfull.size());
optcapability<OptHessian<T> >(this->p)->hessian(expand(x),hfull);
h=hfull.submatrix(freeindex);
}
template<typename T>
NRVec<T> FrozenOptProblem<T>::hessian_times(const NRVec<T> &x, const NRVec<T> &v) const
{
NRVec<T> vfull((T)0,xfull.size());
v.storesubvector_to(freeindex,vfull);
return optcapability<OptHessian<T> >(this->p)->hessian_times(expand(x),vfull).subvector(freeindex);
}
template<typename T>
void FrozenOptProblem<T>::metric(const NRVec<T> &x, NRSMat<T> &g) const
{
NRSMat<T> gfull(xfull.size());
optcapability<OptMetric<T> >(this->p)->metric(expand(x),gfull);
g=gfull.submatrix(freeindex);
}
template<typename T>
int FrozenOptProblem<T>::degree(const int i) const {return optcapability<OptTrigonometric<T> >(this->p)->degree(freeindex[i]);}
template<typename T>
T FrozenOptProblem<T>::period(const int i) const {return optcapability<OptTrigonometric<T> >(this->p)->period(freeindex[i]);}
template<typename T>
int FrozenOptProblem<T>::nconstraints() const {return optcapability<OptConstraints<T> >(this->p)->nconstraints();}
template<typename T>
void FrozenOptProblem<T>::constraints(const NRVec<T> &x, NRVec<T> &c) const {optcapability<OptConstraints<T> >(this->p)->constraints(expand(x),c);}
//central difference (f(x+h e_i) - f(x-h e_i))/(2h); xs is a working copy of x, restored on return
template<typename T>
static T central_difference(const OptProblem<T> *p, NRVec<T> &xs, const NRVec<T> &x, const int i, const T h)
@@ -1553,6 +1612,7 @@ template double linesearch_wolfe(optevaluator<double> &e, NRVec<double> &x, doub
template class optstate<double>;
template class ParameterShift<double>;
template class FiniteDifference<double>;
template class FrozenOptProblem<double>;
template class BFGS_Optimizer<double>;
template class LBFGS_Optimizer<double>;
template class GDIIS_Optimizer<double>;
+31 -2
View File
@@ -28,6 +28,7 @@
#include "vec.h"
#include "mat.h"
#include "smat.h"
#include "bitvector.h"
namespace LA {
@@ -90,19 +91,47 @@ class OptProblemAdapter : public OptProblem<T> {
public:
const OptProblem<T> *p; //wrapped problem
OptProblemAdapter(const OptProblem<T> *pp) : p(pp) {};
virtual bool transforms_capabilities() const {return false;} //the adapter provides the capabilities of the wrapped problem in a transformed form (e.g. other dimension), not the original ones
};
//capability C (OptHessian<T>, OptMetric<T>, ...) of the problem, searched also through adapters; NULL if not available
//a transforming adapter provides C only if the wrapped problem does, and never passes on the untransformed one
template<class C, typename T>
const C *optcapability(const OptProblem<T> *o)
{
const C *c=dynamic_cast<const C *>(o);
if(c) return c;
const OptProblemAdapter<T> *a=dynamic_cast<const OptProblemAdapter<T> *>(o);
return a ? optcapability<C>(a->p) : NULL;
if(!a) return c;
if(a->transforms_capabilities()) return c && optcapability<C>(a->p) ? c : NULL;
return c ? c : optcapability<C>(a->p);
}
//optimization in the free parameters only, the frozen ones keep their values from the full parameter vector
//the Hessian, metric, trigonometric structure and constraints of the wrapped problem are provided for the free parameters
//usage: FrozenOptProblem<T> fp(&problem,x,frozen); NRVec<T> xr=fp.reduce(x); optimizer.minimize(&fp,xr); x=fp.expand(xr);
template<typename T>
class FrozenOptProblem : public OptProblemAdapter<T>, public OptHessian<T>, public OptMetric<T>, public OptTrigonometric<T>, public OptConstraints<T> {
public:
NRVec<T> xfull; //full parameter vector, the values of the frozen parameters are taken from it
NRVec<int> freeindex; //indices of the free parameters in the full vector
FrozenOptProblem(const OptProblem<T> *pp, const NRVec<T> &x, const bitvector &frozen) : OptProblemAdapter<T>(pp), xfull(x) {setfrozen(frozen);};
void setfrozen(const bitvector &frozen); //set of frozen parameters, bit i set = x_i frozen
void setfull(const NRVec<T> &x) {xfull=x;} //new values of the frozen parameters
NRVec<T> reduce(const NRVec<T> &full) const {return full.subvector(freeindex);} //free parameters of a full vector
NRVec<T> expand(const NRVec<T> &reduced) const {NRVec<T> full(xfull); reduced.storesubvector_to(freeindex,full); return full;} //full vector with the frozen values
virtual bool transforms_capabilities() const {return true;}
virtual T funcgrad(const NRVec<T> &x, NRVec<T> *grad) const;
virtual void hessian(const NRVec<T> &x, NRSMat<T> &h) const;
virtual NRVec<T> hessian_times(const NRVec<T> &x, const NRVec<T> &v) const;
virtual void metric(const NRVec<T> &x, NRSMat<T> &g) const;
virtual int degree(const int i) const;
virtual T period(const int i) const;
virtual int nconstraints() const;
virtual void constraints(const NRVec<T> &x, NRVec<T> &c) const;
};
//gradient by the general parameter-shift rule (Wierichs et al., Quantum 6, 677 (2022); degree 1: Mitarai et al., Phys. Rev. A 98, 032309 (2018),
//Schuld et al., Phys. Rev. A 99, 032331 (2019)), exact from 2R_i evaluations per parameter; parameters of degree 0 by central differences with step h
//the wrapped problem has to provide OptTrigonometric
+22
View File
@@ -4882,6 +4882,28 @@ double r=problem.funcgrad(x,NULL);
cout <<r<<endl;
}
if(1) //testbed of the optimization with frozen parameters
{
const int n=10;
NRVec<double> x(n);
x.randomize(1.);
testproblem<double> problem;
bitvector frozen(n);
for(int i=0; i<n; i+=3) frozen.set(i); //every third parameter frozen
FrozenOptProblem<double> fproblem(&problem,x,frozen);
NRVec<double> xr=fproblem.reduce(x);
BFGS_hyperparameters<double> param;
param.my_g_thr=1e-6;
cout << "initial " <<x<<endl;
BFGS_Optimizer<double> o;
optresult<double> res=o.minimize(&fproblem,xr,&param);
NRVec<double> xopt=fproblem.expand(xr);
cout << "optimized " <<xopt<<endl;
cout << "converged "<<res.converged<<" f= "<<res.f<<endl;
const NRVec<double> &xc=x; //x is shared with fproblem.xfull, read-only access
for(int i=0; i<n; ++i) if(frozen[i] && xopt[i]!=xc[i]) cout <<"ERROR: frozen parameter "<<i<<" changed\n";
}
if(1) //testbed of the VQE methods, the problem provides capabilities (OptMetric, OptTrigonometric, OptHessian)
{
int seed;
+83
View File
@@ -611,6 +611,88 @@ check("QNG converges on testproblem_vqe",conv && f< -5.4,f);
}
static void test_frozen()
{
cout <<"--- optimization with frozen parameters (FrozenOptProblem)\n";
testproblem_vqe_constrained<double> vqe;
const int n=8;
NRVec<double> x=startpoint(n,4,2.);
bitvector frozen(n);
frozen.set(1); frozen.set(4); frozen.set(5);
FrozenOptProblem<double> fp(&vqe,x,frozen);
NRVec<double> xr=fp.reduce(x);
NRVec<double> xe=fp.expand(xr);
xe-=x;
check("reduce and expand",xr.size()==5 && xe.norm()==0);
NRVec<double> y=startpoint(5,7); //other values of the free parameters
NRVec<double> yfull=fp.expand(y);
NRVec<double> gfull(n), g(5);
double ffull=vqe.funcgrad(yfull,&gfull), f=fp.funcgrad(y,&g);
NRVec<double> gd=gfull.subvector(fp.freeindex)-g;
check("reduced function value and gradient",f==ffull && gd.norm()==0);
NRSMat<double> hfull(n), h(5), mfull(n), m(5);
vqe.hessian(yfull,hfull); fp.hessian(y,h);
vqe.metric(yfull,mfull); fp.metric(y,m);
NRSMat<double> hd=hfull.submatrix(fp.freeindex)-h, md=mfull.submatrix(fp.freeindex)-m;
check("reduced Hessian and metric",hd.norm()==0 && md.norm()==0);
NRVec<double> v=startpoint(5,8), hv=fp.hessian_times(y,v), hv2=h*v;
hv-=hv2;
check("reduced Hessian-vector product",hv.norm()<1e-14,hv.norm());
NRVec<double> cfull(1), c(1);
vqe.constraints(yfull,cfull); fp.constraints(y,c);
check("constraints and trigonometric degree of the reduced problem",fp.nconstraints()==1 && c[0]==cfull[0] && fp.degree(2)==1 && fp.period(2)==2*M_PI);
testproblem<double> tp;
FrozenOptProblem<double> ftp(&tp,startpoint(6,1),bitvector(6));
ParameterShift<double> ps(&fp);
check("capabilities of the frozen problem only if the wrapped problem has them",optcapability<OptMetric<double> >(&fp)==&fp && optcapability<OptMetric<double> >(&ftp)==NULL && optcapability<OptHessian<double> >(&ftp)==NULL && optcapability<OptMetric<double> >(&ps)==&fp);
//optimizations keep the frozen parameters
{
bitvector fr(10);
for(int i=0; i<10; i+=3) fr.set(i);
NRVec<double> x0=startpoint(10,1);
FrozenOptProblem<double> fq(&tp,x0,fr);
NRVec<double> z=fq.reduce(x0);
BFGS_hyperparameters<double> hb; hb.my_g_thr=1e-8;
BFGS_Optimizer<double> o;
bool conv;
run(o,&fq,z,hb,NULL,&conv);
NRVec<double> zf=fq.expand(z), gz(z.size());
fq.funcgrad(z,&gz);
bool kept=true;
const NRVec<double> &x0c=x0; //x0 is shared with fq.xfull, read-only access
for(int i=0; i<10; i+=3) if(zf[i]!=x0c[i]) kept=false;
check("BFGS with frozen parameters: frozen ones kept, reduced gradient zero",conv && kept && gz.norm()<1e-8,gz.norm());
}
{
NRVec<double> z=fp.reduce(x);
QNG_hyperparameters<double> hq; hq.my_maxstep=10000;
QNG_Optimizer<double> o;
bool conv;
testproblem_vqe<double> vu;
FrozenOptProblem<double> fu(&vu,x,frozen);
run(o,&fu,z,hq,NULL,&conv);
NRVec<double> gz(5);
fu.funcgrad(z,&gz);
check("QNG uses the reduced metric",conv && gz.norm()<1e-5,gz.norm());
z=fp.reduce(x);
Rotosolve_hyperparameters<double> hr;
Rotosolve_Optimizer<double> orot;
run(orot,&fu,z,hr,NULL,&conv);
fu.funcgrad(z,&gz);
check("Rotosolve uses the reduced trigonometric structure",conv && gz.norm()<1e-4,gz.norm());
z=fp.reduce(x);
Cobyla_hyperparameters<double> hc; hc.my_rhoend=1e-8;
Cobyla_Optimizer<double> oc;
run(oc,&fp,z,hc,NULL,&conv);
NRVec<double> cz(1);
fp.constraints(z,cz);
NRVec<double> zf=fp.expand(z);
const NRVec<double> &xc=x; //x is shared with fp.xfull, read-only access
check("COBYLA with frozen parameters satisfies the constraint and keeps them",conv && cz[0]> -1e-10 && zf[1]==xc[1] && zf[4]==xc[4] && zf[5]==xc[5],cz[0]);
}
}
static void test_maxfunc_and_parameters()
{
cout <<"--- evaluation budget and hyperparameters\n";
@@ -725,6 +807,7 @@ test_linemin();
test_gradient_methods();
test_derivative_free();
test_firstorder();
test_frozen();
test_maxfunc_and_parameters();
test_diis();
test_copyonwrite();