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 $U$ of of the system should be used as ARG.
30 :
31 : $$
32 : \Delta u_{\beta'}=(\beta'-\beta) U\, ,
33 : $$
34 :
35 : where $\beta'$ are the temperatures to be sampled and $\beta$ 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 [ENERGY](ENERGY.md) colvar $U=E$, and you will run a multicanonical simulation.
37 : If instead the simulation is at fixed pressure $p$, the contribution of the volume must be added $U=E+pV$ (see example below).
38 :
39 : By defauly the needed steps in temperatures are automatically guessed from few initial unbiased MD steps, as descibed in the paper cited below.
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. [REWEIGHT_TEMP_PRESS](REWEIGHT_TEMP_PRESS.md).
46 : A similar target distribution can be sampled using [TD_MULTICANONICAL](TD_MULTICANONICAL.md).
47 :
48 : ## Examples
49 :
50 : Fixed volume, multicanonical simulation:
51 :
52 : ```plumed
53 : ene: ENERGY
54 : ecv: ECV_MULTITHERMAL ARG=ene TEMP=300 TEMP_MIN=300 TEMP_MAX=800
55 : opes: OPES_EXPANDED ARG=ecv.ene PACE=500
56 : ```
57 :
58 : which, if your MD code passes the temperature to PLUMED, is equivalent to:
59 :
60 : ```plumed
61 : ene: ENERGY
62 : ecv: ECV_MULTITHERMAL ARG=ene TEMP_MAX=800
63 : opes: OPES_EXPANDED ARG=ecv.ene PACE=500
64 : ```
65 :
66 : If instead the pressure is fixed and the volume changes, you shuld calculate the internal energy first, $U=E+pV$
67 :
68 : ```plumed
69 : ene: ENERGY
70 : vol: VOLUME
71 : intEne: CUSTOM PERIODIC=NO ARG=ene,vol FUNC=x+0.06022140857*y
72 : ecv: ECV_MULTITHERMAL ARG=intEne TEMP_MAX=800
73 : opes: OPES_EXPANDED ARG=ecv.intEne PACE=500
74 : ```
75 :
76 : Notice that $p=0.06022140857$ corresponds to 1 bar when using the default PLUMED units.
77 :
78 : */
79 : //+ENDPLUMEDOC
80 :
81 : class ECVmultiThermal :
82 : public ExpansionCVs {
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 13 : ExpansionCVs::registerKeywords(keys);
105 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");
106 13 : keys.add("optional","TEMP_MIN","the minimum of the temperature range");
107 13 : keys.add("optional","TEMP_MAX","the maximum of the temperature range");
108 13 : keys.add("optional","TEMP_STEPS","the number of steps in temperature");
109 13 : keys.add("optional","TEMP_SET_ALL","manually set all the temperatures");
110 13 : keys.addFlag("NO_GEOM_SPACING",false,"do not use geometrical spacing in temperature, but instead linear spacing in inverse temperature");
111 13 : keys.addDOI("10.1103/PhysRevX.10.041034");
112 13 : }
113 :
114 11 : ECVmultiThermal::ECVmultiThermal(const ActionOptions&ao)
115 : : Action(ao)
116 : , ExpansionCVs(ao)
117 11 : , todoAutomatic_(false) {
118 11 : plumed_massert(getNumberOfArguments()==1,"only the internal energy should be given as ARG");
119 :
120 : //set temp0
121 11 : const double temp0=kbt_/getKBoltzmann();
122 :
123 : //parse temp range
124 11 : double temp_min=-1;
125 11 : double temp_max=-1;
126 11 : parse("TEMP_MIN",temp_min);
127 11 : parse("TEMP_MAX",temp_max);
128 11 : unsigned temp_steps=0;
129 22 : parse("TEMP_STEPS",temp_steps);
130 : std::vector<double> temps;
131 11 : parseVector("TEMP_SET_ALL",temps);
132 11 : parseFlag("NO_GEOM_SPACING",geom_spacing_);
133 11 : geom_spacing_=!geom_spacing_;
134 :
135 11 : checkRead();
136 :
137 : //set the intermediate temperatures
138 11 : if(temps.size()>0) {
139 2 : plumed_massert(temp_steps==0,"cannot set both TEMP_STEPS and TEMP_SET_ALL");
140 2 : plumed_massert(temp_min==-1 && temp_max==-1,"cannot set both TEMP_SET_ALL and TEMP_MIN/MAX");
141 2 : plumed_massert(temps.size()>=2,"set at least 2 temperatures");
142 2 : temp_min=temps[0];
143 2 : temp_max=temps[temps.size()-1];
144 2 : derECVs_.resize(temps.size());
145 10 : for(unsigned k=0; k<derECVs_.size(); k++) {
146 8 : derECVs_[k]=(temp0/temps[k]-1.)/kbt_;
147 8 : if(k<derECVs_.size()-1) {
148 6 : plumed_massert(temps[k]<=temps[k+1],"TEMP_SET_ALL must be properly ordered");
149 : }
150 : }
151 : } else {
152 : //get TEMP_MIN and TEMP_MAX
153 9 : plumed_massert(temp_min!=-1 || temp_max!=-1,"TEMP_MIN, TEMP_MAX or both, should be set");
154 9 : if(temp_min==-1) {
155 2 : temp_min=temp0;
156 2 : log.printf(" no TEMP_MIN provided, using TEMP_MIN=TEMP\n");
157 : }
158 9 : if(temp_max==-1) {
159 0 : temp_max=temp0;
160 0 : log.printf(" no TEMP_MAX provided, using TEMP_MAX=TEMP\n");
161 : }
162 9 : plumed_massert(temp_max>=temp_min,"TEMP_MAX should be bigger than TEMP_MIN");
163 9 : derECVs_.resize(2);
164 9 : derECVs_[0]=(temp0/temp_min-1.)/kbt_;
165 9 : derECVs_[1]=(temp0/temp_max-1.)/kbt_;
166 9 : if(temp_min==temp_max && temp_steps==0) {
167 0 : temp_steps=1;
168 : }
169 9 : if(temp_steps>0) {
170 14 : derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
171 : } else {
172 2 : todoAutomatic_=true;
173 : }
174 : }
175 : const double tol=1e-3; //if temp is taken from MD engine it might be numerically slightly different
176 11 : if(temp0<(1-tol)*temp_min || temp0>(1+tol)*temp_max) {
177 0 : log.printf(" +++ WARNING +++ running at TEMP=%g which is outside the chosen temperature range\n",temp0);
178 : }
179 :
180 : //print some info
181 11 : log.printf(" targeting a temperature range from TEMP_MIN=%g to TEMP_MAX=%g\n",temp_min,temp_max);
182 11 : if(!geom_spacing_) {
183 1 : log.printf(" -- NO_GEOM_SPACING: inverse temperatures will be linearly spaced\n");
184 : }
185 11 : }
186 :
187 586 : void ECVmultiThermal::calculateECVs(const double * ene) {
188 3618 : for(unsigned k=0; k<derECVs_.size(); k++) {
189 3032 : ECVs_[k]=derECVs_[k]*ene[0];
190 : }
191 : // derivatives never change: derECVs_k=(beta_k-beta0)
192 586 : }
193 :
194 11 : const double * ECVmultiThermal::getPntrToECVs(unsigned j) {
195 11 : plumed_massert(isReady_,"cannot access ECVs before initialization");
196 11 : plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
197 11 : return &ECVs_[0];
198 : }
199 :
200 11 : const double * ECVmultiThermal::getPntrToDerECVs(unsigned j) {
201 11 : plumed_massert(isReady_,"cannot access ECVs before initialization");
202 11 : plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
203 11 : return &derECVs_[0];
204 : }
205 :
206 11 : std::vector<std::string> ECVmultiThermal::getLambdas() const {
207 11 : plumed_massert(!todoAutomatic_,"cannot access lambdas before initializing them");
208 11 : const double temp0=kbt_/getKBoltzmann();
209 11 : std::vector<std::string> lambdas(derECVs_.size());
210 70 : for(unsigned k=0; k<derECVs_.size(); k++) {
211 59 : std::ostringstream subs;
212 59 : subs<<temp0/(derECVs_[k]*kbt_+1); //temp_k
213 59 : lambdas[k]=subs.str();
214 59 : }
215 11 : return lambdas;
216 0 : }
217 :
218 11 : void ECVmultiThermal::initECVs() {
219 11 : plumed_massert(!isReady_,"initialization should not be called twice");
220 11 : plumed_massert(!todoAutomatic_,"this should not happen");
221 11 : totNumECVs_=derECVs_.size();
222 11 : ECVs_.resize(derECVs_.size());
223 11 : isReady_=true;
224 11 : log.printf(" *%4lu temperatures for %s\n",derECVs_.size(),getName().c_str());
225 11 : }
226 :
227 8 : void ECVmultiThermal::initECVs_observ(const std::vector<double>& all_obs_cvs,const unsigned ncv,const unsigned index_j) {
228 8 : if(todoAutomatic_) { //estimate the steps in beta from observations
229 1 : plumed_massert(all_obs_cvs.size()%ncv==0 && index_j<ncv,"initECVs_observ parameters are inconsistent");
230 1 : std::vector<double> obs_ene(all_obs_cvs.size()/ncv); //copy only useful observation (would be better not to copy...)
231 11 : for(unsigned t=0; t<obs_ene.size(); t++) {
232 10 : obs_ene[t]=all_obs_cvs[t*ncv+index_j];
233 : }
234 1 : const unsigned temp_steps=estimateNumSteps(derECVs_[0],derECVs_[1],obs_ene,"TEMP");
235 1 : log.printf(" (spacing is in beta, not in temperature)\n");
236 2 : derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
237 1 : todoAutomatic_=false;
238 : }
239 8 : initECVs();
240 8 : calculateECVs(&all_obs_cvs[index_j]);
241 8 : }
242 :
243 3 : void ECVmultiThermal::initECVs_restart(const std::vector<std::string>& lambdas) {
244 3 : std::size_t pos=lambdas[0].find("_");
245 3 : plumed_massert(pos==std::string::npos,"this should not happen, only one CV is used in "+getName());
246 3 : if(todoAutomatic_) {
247 2 : derECVs_=getSteps(derECVs_[0],derECVs_[1],lambdas.size(),"TEMP",geom_spacing_,1./kbt_);
248 1 : todoAutomatic_=false;
249 : }
250 3 : std::vector<std::string> myLambdas=getLambdas();
251 3 : plumed_massert(myLambdas.size()==lambdas.size(),"RESTART - mismatch in number of "+getName());
252 3 : plumed_massert(std::equal(myLambdas.begin(),myLambdas.end(),lambdas.begin()),"RESTART - mismatch in lambda values of "+getName());
253 :
254 3 : initECVs();
255 3 : }
256 :
257 : }
258 : }
|