optimize: freezing parameters in a problem using a bitvector
This commit is contained in:
1 parent
0900678e5c
commit
3ef603c184
4 files changed
+196
-2
No files matched your search
+60
@@ -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
@@ -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
|
||||
|
||||
@@ -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,¶m);
|
||||
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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user