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