Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2020-2021 of Michele Invernizzi.
3 :
4 : This file is part of the OPES plumed module.
5 :
6 : The OPES plumed module is free software: you can redistribute it and/or modify
7 : it under the terms of the GNU Lesser General Public License as published by
8 : the Free Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : The OPES plumed module is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU Lesser General Public License for more details.
15 :
16 : You should have received a copy of the GNU Lesser General Public License
17 : along with plumed. If not, see <http://www.gnu.org/licenses/>.
18 : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
19 : #include "ExpansionCVs.h"
20 : #include "core/ActionRegister.h"
21 :
22 : namespace PLMD {
23 : namespace opes {
24 :
25 : //+PLUMEDOC OPES_EXPANSION_CV ECV_MULTITHERMAL
26 : /*
27 : Expand a simulation to sample multiple temperatures simultaneously.
28 :
29 : The internal energy \f$U\f$ of of the system should be used as ARG.
30 : \f[
31 : \Delta u_{\beta'}=(\beta'-\beta) U\, ,
32 : \f]
33 : where \f$\beta'\f$ are the temperatures to be sampled and \f$\beta\f$ is the temperature at which the simulation is conducted.
34 : In case of fixed volume, the internal energy is simply the potential energy given by the \ref ENERGY colvar\f$U=E\f$, and you will run a multicanonical simulation.
35 : If instead the simulation is at fixed pressure \f$p\f$, the contribution of the volume must be added \f$U=E+pV\f$ (see example below).
36 :
37 : By defauly the needed steps in temperatures are automatically guessed from few initial unbiased MD steps, as descibed in \cite Invernizzi2020unified.
38 : Otherwise you can manually set this number with TEMP_STEPS.
39 : In both cases the steps will be geometrically spaced in temperature.
40 : Use instead the keyword NO_GEOM_SPACING for a linear spacing in the inverse temperature (beta), that typically increases the focus on lower temperatures.
41 : Finally, you can use instead the keyword TEMP_SET_ALL and explicitly provide each temperature.
42 :
43 : You can reweight the resulting simulation at any temperature in the chosen range, using e.g. \ref REWEIGHT_TEMP_PRESS.
44 : A similar target distribution can be sampled using \ref TD_MULTICANONICAL.
45 :
46 :
47 : \par Examples
48 :
49 : Fixed volume, multicanonical simulation:
50 :
51 : \plumedfile
52 : ene: ENERGY
53 : ecv: ECV_MULTITHERMAL ARG=ene TEMP=300 TEMP_MIN=300 TEMP_MAX=800
54 : opes: OPES_EXPANDED ARG=ecv.ene PACE=500
55 : \endplumedfile
56 :
57 : which, if your MD code passes the temperature to PLUMED, is equivalent to:
58 :
59 : \plumedfile
60 : ene: ENERGY
61 : ecv: ECV_MULTITHERMAL ARG=ene TEMP_MAX=800
62 : opes: OPES_EXPANDED ARG=ecv.ene PACE=500
63 : \endplumedfile
64 :
65 : If instead the pressure is fixed and the volume changes, you shuld calculate the internal energy first, \f$U=E+pV\f$
66 :
67 : \plumedfile
68 : ene: ENERGY
69 : vol: VOLUME
70 : intEne: CUSTOM PERIODIC=NO ARG=ene,vol FUNC=x+0.06022140857*y
71 : ecv: ECV_MULTITHERMAL ARG=intEne TEMP_MAX=800
72 : opes: OPES_EXPANDED ARG=ecv.intEne PACE=500
73 : \endplumedfile
74 :
75 : Notice that \f$p=0.06022140857\f$ corresponds to 1 bar when using the default PLUMED units.
76 :
77 : */
78 : //+ENDPLUMEDOC
79 :
80 : class ECVmultiThermal :
81 : public ExpansionCVs
82 : {
83 : private:
84 : bool todoAutomatic_;
85 : bool geom_spacing_;
86 : std::vector<double> ECVs_;
87 : std::vector<double> derECVs_; //(beta_k-beta0) or (temp0/temp_k-1)/kbt
88 : void initECVs();
89 :
90 : public:
91 : explicit ECVmultiThermal(const ActionOptions&);
92 : static void registerKeywords(Keywords& keys);
93 : void calculateECVs(const double *) override;
94 : const double * getPntrToECVs(unsigned) override;
95 : const double * getPntrToDerECVs(unsigned) override;
96 : std::vector<std::string> getLambdas() const override;
97 : void initECVs_observ(const std::vector<double>&,const unsigned,const unsigned) override;
98 : void initECVs_restart(const std::vector<std::string>&) override;
99 : };
100 :
101 : PLUMED_REGISTER_ACTION(ECVmultiThermal,"ECV_MULTITHERMAL")
102 :
103 13 : void ECVmultiThermal::registerKeywords(Keywords& keys)
104 : {
105 13 : ExpansionCVs::registerKeywords(keys);
106 13 : keys.remove("ARG");
107 26 : keys.add("compulsory","ARG","the label of the internal energy of the system. If volume is fixed it is calculated by the ENERGY colvar");
108 26 : keys.add("optional","TEMP_MIN","the minimum of the temperature range");
109 26 : keys.add("optional","TEMP_MAX","the maximum of the temperature range");
110 26 : keys.add("optional","TEMP_STEPS","the number of steps in temperature");
111 26 : keys.add("optional","TEMP_SET_ALL","manually set all the temperatures");
112 26 : keys.addFlag("NO_GEOM_SPACING",false,"do not use geometrical spacing in temperature, but instead linear spacing in inverse temperature");
113 13 : }
114 :
115 11 : ECVmultiThermal::ECVmultiThermal(const ActionOptions&ao)
116 : : Action(ao)
117 : , ExpansionCVs(ao)
118 11 : , todoAutomatic_(false)
119 : {
120 11 : plumed_massert(getNumberOfArguments()==1,"only the internal energy should be given as ARG");
121 :
122 : //set temp0
123 11 : const double temp0=kbt_/getKBoltzmann();
124 :
125 : //parse temp range
126 11 : double temp_min=-1;
127 11 : double temp_max=-1;
128 11 : parse("TEMP_MIN",temp_min);
129 11 : parse("TEMP_MAX",temp_max);
130 11 : unsigned temp_steps=0;
131 22 : parse("TEMP_STEPS",temp_steps);
132 : std::vector<double> temps;
133 11 : parseVector("TEMP_SET_ALL",temps);
134 11 : parseFlag("NO_GEOM_SPACING",geom_spacing_);
135 11 : geom_spacing_=!geom_spacing_;
136 :
137 11 : checkRead();
138 :
139 : //set the intermediate temperatures
140 11 : if(temps.size()>0)
141 : {
142 2 : plumed_massert(temp_steps==0,"cannot set both TEMP_STEPS and TEMP_SET_ALL");
143 2 : plumed_massert(temp_min==-1 && temp_max==-1,"cannot set both TEMP_SET_ALL and TEMP_MIN/MAX");
144 2 : plumed_massert(temps.size()>=2,"set at least 2 temperatures");
145 2 : temp_min=temps[0];
146 2 : temp_max=temps[temps.size()-1];
147 2 : derECVs_.resize(temps.size());
148 10 : for(unsigned k=0; k<derECVs_.size(); k++)
149 : {
150 8 : derECVs_[k]=(temp0/temps[k]-1.)/kbt_;
151 8 : if(k<derECVs_.size()-1)
152 6 : plumed_massert(temps[k]<=temps[k+1],"TEMP_SET_ALL must be properly ordered");
153 : }
154 : }
155 : else
156 : { //get TEMP_MIN and TEMP_MAX
157 9 : plumed_massert(temp_min!=-1 || temp_max!=-1,"TEMP_MIN, TEMP_MAX or both, should be set");
158 9 : if(temp_min==-1)
159 : {
160 2 : temp_min=temp0;
161 2 : log.printf(" no TEMP_MIN provided, using TEMP_MIN=TEMP\n");
162 : }
163 9 : if(temp_max==-1)
164 : {
165 0 : temp_max=temp0;
166 0 : log.printf(" no TEMP_MAX provided, using TEMP_MAX=TEMP\n");
167 : }
168 9 : plumed_massert(temp_max>=temp_min,"TEMP_MAX should be bigger than TEMP_MIN");
169 9 : derECVs_.resize(2);
170 9 : derECVs_[0]=(temp0/temp_min-1.)/kbt_;
171 9 : derECVs_[1]=(temp0/temp_max-1.)/kbt_;
172 9 : if(temp_min==temp_max && temp_steps==0)
173 0 : temp_steps=1;
174 9 : if(temp_steps>0)
175 14 : derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
176 : else
177 2 : todoAutomatic_=true;
178 : }
179 : const double tol=1e-3; //if temp is taken from MD engine it might be numerically slightly different
180 11 : if(temp0<(1-tol)*temp_min || temp0>(1+tol)*temp_max)
181 0 : log.printf(" +++ WARNING +++ running at TEMP=%g which is outside the chosen temperature range\n",temp0);
182 :
183 : //print some info
184 11 : log.printf(" targeting a temperature range from TEMP_MIN=%g to TEMP_MAX=%g\n",temp_min,temp_max);
185 11 : if(!geom_spacing_)
186 1 : log.printf(" -- NO_GEOM_SPACING: inverse temperatures will be linearly spaced\n");
187 11 : }
188 :
189 586 : void ECVmultiThermal::calculateECVs(const double * ene)
190 : {
191 3618 : for(unsigned k=0; k<derECVs_.size(); k++)
192 3032 : ECVs_[k]=derECVs_[k]*ene[0];
193 : // derivatives never change: derECVs_k=(beta_k-beta0)
194 586 : }
195 :
196 11 : const double * ECVmultiThermal::getPntrToECVs(unsigned j)
197 : {
198 11 : plumed_massert(isReady_,"cannot access ECVs before initialization");
199 11 : plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
200 11 : return &ECVs_[0];
201 : }
202 :
203 11 : const double * ECVmultiThermal::getPntrToDerECVs(unsigned j)
204 : {
205 11 : plumed_massert(isReady_,"cannot access ECVs before initialization");
206 11 : plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
207 11 : return &derECVs_[0];
208 : }
209 :
210 11 : std::vector<std::string> ECVmultiThermal::getLambdas() const
211 : {
212 11 : plumed_massert(!todoAutomatic_,"cannot access lambdas before initializing them");
213 11 : const double temp0=kbt_/getKBoltzmann();
214 11 : std::vector<std::string> lambdas(derECVs_.size());
215 70 : for(unsigned k=0; k<derECVs_.size(); k++)
216 : {
217 59 : std::ostringstream subs;
218 59 : subs<<temp0/(derECVs_[k]*kbt_+1); //temp_k
219 59 : lambdas[k]=subs.str();
220 59 : }
221 11 : return lambdas;
222 0 : }
223 :
224 11 : void ECVmultiThermal::initECVs()
225 : {
226 11 : plumed_massert(!isReady_,"initialization should not be called twice");
227 11 : plumed_massert(!todoAutomatic_,"this should not happen");
228 11 : totNumECVs_=derECVs_.size();
229 11 : ECVs_.resize(derECVs_.size());
230 11 : isReady_=true;
231 11 : log.printf(" *%4lu temperatures for %s\n",derECVs_.size(),getName().c_str());
232 11 : }
233 :
234 8 : void ECVmultiThermal::initECVs_observ(const std::vector<double>& all_obs_cvs,const unsigned ncv,const unsigned index_j)
235 : {
236 8 : if(todoAutomatic_) //estimate the steps in beta from observations
237 : {
238 1 : plumed_massert(all_obs_cvs.size()%ncv==0 && index_j<ncv,"initECVs_observ parameters are inconsistent");
239 1 : std::vector<double> obs_ene(all_obs_cvs.size()/ncv); //copy only useful observation (would be better not to copy...)
240 11 : for(unsigned t=0; t<obs_ene.size(); t++)
241 10 : obs_ene[t]=all_obs_cvs[t*ncv+index_j];
242 1 : const unsigned temp_steps=estimateNumSteps(derECVs_[0],derECVs_[1],obs_ene,"TEMP");
243 1 : log.printf(" (spacing is in beta, not in temperature)\n");
244 2 : derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
245 1 : todoAutomatic_=false;
246 : }
247 8 : initECVs();
248 8 : calculateECVs(&all_obs_cvs[index_j]);
249 8 : }
250 :
251 3 : void ECVmultiThermal::initECVs_restart(const std::vector<std::string>& lambdas)
252 : {
253 3 : std::size_t pos=lambdas[0].find("_");
254 3 : plumed_massert(pos==std::string::npos,"this should not happen, only one CV is used in "+getName());
255 3 : if(todoAutomatic_)
256 : {
257 2 : derECVs_=getSteps(derECVs_[0],derECVs_[1],lambdas.size(),"TEMP",geom_spacing_,1./kbt_);
258 1 : todoAutomatic_=false;
259 : }
260 3 : std::vector<std::string> myLambdas=getLambdas();
261 3 : plumed_massert(myLambdas.size()==lambdas.size(),"RESTART - mismatch in number of "+getName());
262 3 : plumed_massert(std::equal(myLambdas.begin(),myLambdas.end(),lambdas.begin()),"RESTART - mismatch in lambda values of "+getName());
263 :
264 3 : initECVs();
265 3 : }
266 :
267 : }
268 : }
|