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 26 : keys.addInputKeyword("compulsory","ARG","scalar","the label of the internal energy of the system. If volume is fixed it is calculated by the ENERGY colvar");
107 26 : keys.add("optional","TEMP_MIN","the minimum of the temperature range");
108 26 : keys.add("optional","TEMP_MAX","the maximum of the temperature range");
109 26 : keys.add("optional","TEMP_STEPS","the number of steps in temperature");
110 26 : keys.add("optional","TEMP_SET_ALL","manually set all the temperatures");
111 26 : keys.addFlag("NO_GEOM_SPACING",false,"do not use geometrical spacing in temperature, but instead linear spacing in inverse temperature");
112 13 : }
113 :
114 11 : ECVmultiThermal::ECVmultiThermal(const ActionOptions&ao)
115 : : Action(ao)
116 : , ExpansionCVs(ao)
117 11 : , todoAutomatic_(false)
118 : {
119 11 : plumed_massert(getNumberOfArguments()==1,"only the internal energy should be given as ARG");
120 :
121 : //set temp0
122 11 : const double temp0=kbt_/getKBoltzmann();
123 :
124 : //parse temp range
125 11 : double temp_min=-1;
126 11 : double temp_max=-1;
127 11 : parse("TEMP_MIN",temp_min);
128 11 : parse("TEMP_MAX",temp_max);
129 11 : unsigned temp_steps=0;
130 22 : parse("TEMP_STEPS",temp_steps);
131 : std::vector<double> temps;
132 11 : parseVector("TEMP_SET_ALL",temps);
133 11 : parseFlag("NO_GEOM_SPACING",geom_spacing_);
134 11 : geom_spacing_=!geom_spacing_;
135 :
136 11 : checkRead();
137 :
138 : //set the intermediate temperatures
139 11 : if(temps.size()>0)
140 : {
141 2 : plumed_massert(temp_steps==0,"cannot set both TEMP_STEPS and TEMP_SET_ALL");
142 2 : plumed_massert(temp_min==-1 && temp_max==-1,"cannot set both TEMP_SET_ALL and TEMP_MIN/MAX");
143 2 : plumed_massert(temps.size()>=2,"set at least 2 temperatures");
144 2 : temp_min=temps[0];
145 2 : temp_max=temps[temps.size()-1];
146 2 : derECVs_.resize(temps.size());
147 10 : for(unsigned k=0; k<derECVs_.size(); k++)
148 : {
149 8 : derECVs_[k]=(temp0/temps[k]-1.)/kbt_;
150 8 : if(k<derECVs_.size()-1)
151 6 : plumed_massert(temps[k]<=temps[k+1],"TEMP_SET_ALL must be properly ordered");
152 : }
153 : }
154 : else
155 : { //get TEMP_MIN and TEMP_MAX
156 9 : plumed_massert(temp_min!=-1 || temp_max!=-1,"TEMP_MIN, TEMP_MAX or both, should be set");
157 9 : if(temp_min==-1)
158 : {
159 2 : temp_min=temp0;
160 2 : log.printf(" no TEMP_MIN provided, using TEMP_MIN=TEMP\n");
161 : }
162 9 : if(temp_max==-1)
163 : {
164 0 : temp_max=temp0;
165 0 : log.printf(" no TEMP_MAX provided, using TEMP_MAX=TEMP\n");
166 : }
167 9 : plumed_massert(temp_max>=temp_min,"TEMP_MAX should be bigger than TEMP_MIN");
168 9 : derECVs_.resize(2);
169 9 : derECVs_[0]=(temp0/temp_min-1.)/kbt_;
170 9 : derECVs_[1]=(temp0/temp_max-1.)/kbt_;
171 9 : if(temp_min==temp_max && temp_steps==0)
172 0 : temp_steps=1;
173 9 : if(temp_steps>0)
174 14 : derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
175 : else
176 2 : todoAutomatic_=true;
177 : }
178 : const double tol=1e-3; //if temp is taken from MD engine it might be numerically slightly different
179 11 : if(temp0<(1-tol)*temp_min || temp0>(1+tol)*temp_max)
180 0 : log.printf(" +++ WARNING +++ running at TEMP=%g which is outside the chosen temperature range\n",temp0);
181 :
182 : //print some info
183 11 : log.printf(" targeting a temperature range from TEMP_MIN=%g to TEMP_MAX=%g\n",temp_min,temp_max);
184 11 : if(!geom_spacing_)
185 1 : log.printf(" -- NO_GEOM_SPACING: inverse temperatures will be linearly spaced\n");
186 11 : }
187 :
188 586 : void ECVmultiThermal::calculateECVs(const double * ene)
189 : {
190 3618 : for(unsigned k=0; k<derECVs_.size(); k++)
191 3032 : ECVs_[k]=derECVs_[k]*ene[0];
192 : // derivatives never change: derECVs_k=(beta_k-beta0)
193 586 : }
194 :
195 11 : const double * ECVmultiThermal::getPntrToECVs(unsigned j)
196 : {
197 11 : plumed_massert(isReady_,"cannot access ECVs before initialization");
198 11 : plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
199 11 : return &ECVs_[0];
200 : }
201 :
202 11 : const double * ECVmultiThermal::getPntrToDerECVs(unsigned j)
203 : {
204 11 : plumed_massert(isReady_,"cannot access ECVs before initialization");
205 11 : plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
206 11 : return &derECVs_[0];
207 : }
208 :
209 11 : std::vector<std::string> ECVmultiThermal::getLambdas() const
210 : {
211 11 : plumed_massert(!todoAutomatic_,"cannot access lambdas before initializing them");
212 11 : const double temp0=kbt_/getKBoltzmann();
213 11 : std::vector<std::string> lambdas(derECVs_.size());
214 70 : for(unsigned k=0; k<derECVs_.size(); k++)
215 : {
216 59 : std::ostringstream subs;
217 59 : subs<<temp0/(derECVs_[k]*kbt_+1); //temp_k
218 59 : lambdas[k]=subs.str();
219 59 : }
220 11 : return lambdas;
221 0 : }
222 :
223 11 : void ECVmultiThermal::initECVs()
224 : {
225 11 : plumed_massert(!isReady_,"initialization should not be called twice");
226 11 : plumed_massert(!todoAutomatic_,"this should not happen");
227 11 : totNumECVs_=derECVs_.size();
228 11 : ECVs_.resize(derECVs_.size());
229 11 : isReady_=true;
230 11 : log.printf(" *%4lu temperatures for %s\n",derECVs_.size(),getName().c_str());
231 11 : }
232 :
233 8 : void ECVmultiThermal::initECVs_observ(const std::vector<double>& all_obs_cvs,const unsigned ncv,const unsigned index_j)
234 : {
235 8 : if(todoAutomatic_) //estimate the steps in beta from observations
236 : {
237 1 : plumed_massert(all_obs_cvs.size()%ncv==0 && index_j<ncv,"initECVs_observ parameters are inconsistent");
238 1 : std::vector<double> obs_ene(all_obs_cvs.size()/ncv); //copy only useful observation (would be better not to copy...)
239 11 : for(unsigned t=0; t<obs_ene.size(); t++)
240 10 : obs_ene[t]=all_obs_cvs[t*ncv+index_j];
241 1 : const unsigned temp_steps=estimateNumSteps(derECVs_[0],derECVs_[1],obs_ene,"TEMP");
242 1 : log.printf(" (spacing is in beta, not in temperature)\n");
243 2 : derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
244 1 : todoAutomatic_=false;
245 : }
246 8 : initECVs();
247 8 : calculateECVs(&all_obs_cvs[index_j]);
248 8 : }
249 :
250 3 : void ECVmultiThermal::initECVs_restart(const std::vector<std::string>& lambdas)
251 : {
252 3 : std::size_t pos=lambdas[0].find("_");
253 3 : plumed_massert(pos==std::string::npos,"this should not happen, only one CV is used in "+getName());
254 3 : if(todoAutomatic_)
255 : {
256 2 : derECVs_=getSteps(derECVs_[0],derECVs_[1],lambdas.size(),"TEMP",geom_spacing_,1./kbt_);
257 1 : todoAutomatic_=false;
258 : }
259 3 : std::vector<std::string> myLambdas=getLambdas();
260 3 : plumed_massert(myLambdas.size()==lambdas.size(),"RESTART - mismatch in number of "+getName());
261 3 : plumed_massert(std::equal(myLambdas.begin(),myLambdas.end(),lambdas.begin()),"RESTART - mismatch in lambda values of "+getName());
262 :
263 3 : initECVs();
264 3 : }
265 :
266 : }
267 : }
|