OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
OHFrequencyMap.cpp
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the following paper when you publish your work:
33 *
34 * [1] Drisko et al., J. Open Source Softw. 9, 7004 (2024).
35 *
36 * Good starting points for code and simulation methodology are:
37 *
38 * [2] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
39 * [3] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
40 * [4] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
41 * [5] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
42 * [6] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
43 * [7] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
44 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
45 * [9] Drisko & Gezelter, J. Chem. Theory Comput. 20, 4986-4997 (2024).
46 */
47
48#include "applications/staticProps/OHFrequencyMap.hpp"
49
50#include <algorithm>
51#include <fstream>
52
55#include "io/DumpReader.hpp"
57#include "utils/simError.h"
58
59namespace OpenMD {
60
61 OHFrequencyMap::OHFrequencyMap(SimInfo* info, const std::string& filename,
62 const std::string& sele1, int nbins) :
63 StaticAnalyser(info, filename, nbins),
64 selectionScript1_(sele1), seleMan1_(info_), evaluator1_(info_) {
65
66 setOutputName(getPrefix(filename) + ".OHfreqs");
67
68 dumpHasElectricFields_ = info_->getSimParams()->getOutputElectricField();
69
70 // If we don't have electric fields stored in the dump file, we
71 // need to expand storage to hold them during a force calculation.
72
73 if(!dumpHasElectricFields_) {
74 int atomStorageLayout = info_->getAtomStorageLayout();
75 int rigidBodyStorageLayout = info->getRigidBodyStorageLayout();
76 int cutoffGroupStorageLayout = info->getCutoffGroupStorageLayout();
77
78 atomStorageLayout |= DataStorage::dslElectricField;
79 rigidBodyStorageLayout |= DataStorage::dslElectricField;
80 info_->setAtomStorageLayout(atomStorageLayout);
81 info_->setRigidBodyStorageLayout(rigidBodyStorageLayout);
82 info_->setCutoffGroupStorageLayout(cutoffGroupStorageLayout);
83
84 info_->setSnapshotManager(new SimSnapshotManager(info_, atomStorageLayout,
85 rigidBodyStorageLayout,
86 cutoffGroupStorageLayout));
87 }
88
89 info_->getSimParams()->setOutputElectricField(true);
90
91 evaluator1_.loadScriptString(sele1);
92 if (!evaluator1_.isDynamic()) {
93 seleMan1_.setSelectionSet(evaluator1_.evaluate());
94 }
95
96 count_.resize(nBins_);
97 histogram_.resize(nBins_);
98
99 minFreq_ = 2700;
100 maxFreq_ = 4000;
101
102 // Original values from "Combined electronic structure/molecular
103 // dynamics approach for ultrafast infrared spectroscopy of dilute
104 // HOD in liquid H2O and D2O," by S. A. Corcelli, C. P. Lawrence,
105 // and J. L. Skinner, J. Chem. Phys. 120, 8107–8117 (2004),
106 // https://doi.org/10.1063/1.1683072
107 //
108 // These map the site electric fields (in atomic units) onto
109 // frequencies in wavenumbers.
110
111 // w10_["H_TIP4P"] = std::make_pair(3832.0, -12141.0);
112 // w10_["H_SPCE"] = std::make_pair(3806.0, -10792.0);
113
114 // Updated SPC/E maps from B. M. Auer, J. L. Skinner; IR and Raman
115 // spectra of liquid water: Theory and
116 // interpretation. J. Chem. Phys. 14 June 2008; 128 (22):
117 // 224511. https://doi.org/10.1063/1.2925258
118
119 w10_["H_SPCE"] = std::make_tuple(3761.6, -5060.4, -86225.0);
120 w21_["H_SPCE"] = std::make_tuple(3614.1, -5493.7, -115670.0);
121 x10_["H_SPCE"] = std::make_pair(0.1024, -0.927e-5);
122 x21_["H_SPCE"] = std::make_pair(0.1428, -1.29e-5);
123 p10_["H_SPCE"] = std::make_pair(1.611, 5.893e-4);
124 p21_["H_SPCE"] = std::make_pair(1.611, 5.893e-4);
125 muprime_["H_SPCE"] = std::make_tuple(0.71116, 75.591, 0.0);
126 wintra_["H_SPCE"] = std::make_tuple(-1789, 23852, -1.966);
127
128 // Updated TIP4P maps from: "Robustness of frequency, transition
129 // dipole, and coupling maps for water vibrational spectroscopy,"
130 // S. M. Gruenbaum, C. J. Tainter, L. Shi, Y. Ni, and
131 // J. L. Skinner, Journal of Chemical Theory and Computation,
132 // 9(7):3109–3117, 07 2013 DOI: 10.1021/ct400292q
133
134 w10_["H_TIP4P"] = std::make_tuple(3760.2, -3541.7, -152677.0);
135 w21_["H_TIP4P"] = std::make_tuple(3606.0, -3498.6, -198715.0);
136 x10_["H_TIP4P"] = std::make_pair(0.19285, -1.7261e-5);
137 x21_["H_TIP4P"] = std::make_pair(0.26836, -2.3788e-5);
138 p10_["H_TIP4P"] = std::make_pair(1.6466, 5.7692e-4);
139 p21_["H_TIP4P"] = std::make_pair(2.0160, 8.7684e-4);
140 muprime_["H_TIP4P"] = std::make_tuple(0.1646, 11.39, 63.41);
141 wintra_["H_TIP4P"] = std::make_tuple(-1361, 27165, -1.887);
142
143 // Assuming the OH frequency map for TIP4P-Ice is the same as
144 // TIP4P. Transferrability was tested by: Tetsuyuki Takayama,
145 // Takuhiro Otosu, Shoichi Yamaguchi; "Transferability of
146 // vibrational spectroscopic map from TIP4P to TIP4P-like water
147 // models," J. Chem. Phys. 7 April 2023; 158 (13): 136101.
148 // https://doi.org/10.1063/5.0146084
149
150 w10_["H_TIP4P-Ice"] = w10_["H_TIP4P"];
151 w21_["H_TIP4P-Ice"] = w21_["H_TIP4P"];
152 x10_["H_TIP4P-Ice"] = x10_["H_TIP4P"];
153 x21_["H_TIP4P-Ice"] = x21_["H_TIP4P"];
154 p10_["H_TIP4P-Ice"] = p10_["H_TIP4P"];
155 p21_["H_TIP4P-Ice"] = p21_["H_TIP4P"];
156 muprime_["H_TIP4P-Ice"] = muprime_["H_TIP4P"];
157 wintra_["H_TIP4P-Ice"] = wintra_["H_TIP4P"];
158
159 ForceField* forceField_ = info_->getForceField();
160 AtomTypeSet atypes = info_->getSimulatedAtomTypes();
161 int nAtoms =
162 info->getSnapshotManager()->getCurrentSnapshot()->getNumberOfAtoms();
163
164 EF_ = V3Zero;
165
166 std::vector<RealType> ef;
167 bool efSpec = false;
168
169 if (info_->getSimParams()->haveElectricField()) {
170 efSpec = true;
171 ef = info_->getSimParams()->getElectricField();
172 }
173 if (info_->getSimParams()->haveUniformField()) {
174 efSpec = true;
175 ef = info_->getSimParams()->getUniformField();
176 }
177 if (efSpec) {
178 if (ef.size() != 3) {
179 snprintf(
180 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
181 "OHFrequencyMap: Incorrect number of parameters specified for "
182 "uniformField.\n"
183 "\tthere should be 3 parameters, but %zu were specified.\n",
184 ef.size());
185 painCave.isFatal = 1;
186 simError();
187 }
188 EF_.x() = ef[0];
189 EF_.y() = ef[1];
190 EF_.z() = ef[2];
191 }
192 }
193
194 void OHFrequencyMap::process() {
195 ForceManager* forceMan;
196 Molecule* mol;
197 Atom* atom;
198 AtomType* atype;
199 SimInfo::MoleculeIterator mi;
200 Atom* atom2;
201 StuntDouble* sd1;
202 int ii, sdID, molID;
203 RealType a0(0.0), a1(0.0), a2(0.0);
204 Vector3d sE(0.0);
205 RealType freq;
206 std::string name;
207 map<string, std::tuple<RealType, RealType, RealType>>::iterator fi;
208 const RealType chrgToKcal = 23.060548;
209 const RealType kcalToAU = 0.0008432975573; // fields in atomic units
210
211 DumpReader reader(info_, dumpFilename_);
212 int nFrames = reader.getNFrames();
213 nProcessed_ = nFrames / step_;
214
215 if (!dumpHasElectricFields_) {
216 // We'll need the force manager to compute the fields
217 forceMan = new ForceManager(info_);
218 forceMan->setDoElectricField(true);
219 forceMan->initialize();
220 }
221
222 std::fill(histogram_.begin(), histogram_.end(), 0.0);
223 std::fill(count_.begin(), count_.end(), 0);
224
225 for (int istep = 0; istep < nFrames; istep += step_) {
226 reader.readFrame(istep);
227 currentSnapshot_ = info_->getSnapshotManager()->getCurrentSnapshot();
228
229 if (!dumpHasElectricFields_) {
230 forceMan->setDoElectricField(true);
231 forceMan->calcForces();
232 }
233
234 if (evaluator1_.isDynamic()) {
235 seleMan1_.setSelectionSet(evaluator1_.evaluate());
236 }
237
238 for (sd1 = seleMan1_.beginSelected(ii); sd1 != NULL;
239 sd1 = seleMan1_.nextSelected(ii)) {
240 sdID = sd1->getGlobalIndex();
241 molID = info_->getGlobalMolMembership(sdID);
242 mol = info_->getMoleculeByGlobalIndex(molID);
243
244 Vector3d Opos = mol->getAtomAt(0)->getPos();
245 Vector3d Hpos = sd1->getPos();
246
247 Vector3d rOH = Hpos - Opos;
248 rOH.normalize();
249
250 if (sd1->isAtom()) {
251 atom = dynamic_cast<Atom*>(sd1);
252 atype = atom->getAtomType();
253 name = atype->getName();
254
255 fi = w10_.find(name);
256 if (fi != w10_.end()) {
257 a0 = get<0>((*fi).second);
258 a1 = get<1>((*fi).second);
259 a2 = get<2>((*fi).second);
260
261 sE = sd1->getElectricField(); // in kcal mol^-1 angstrom^-1 e^-1
262
263 // Add the applied electric field (in Volts / angstrom);
264
265 sE += EF_ * chrgToKcal;
266
267 // convert to atomic units (hartree electrons^-1 bohr^-1):
268
269 sE *= kcalToAU;
270
271 RealType E = dot(rOH, sE);
272
273 freq = a0 + a1 * E + a2 * E*E;
274
275 int binNo =
276 int(nBins_ * (freq - minFreq_) / (maxFreq_ - minFreq_));
277 if (binNo >= 0 && binNo < nBins_) {
278 count_[binNo]++;
279 } else {
280 std::cerr << "freq = " << freq
281 << " is outside histogram range: (" << minFreq_ << ", "
282 << maxFreq_ << ")\n";
283 }
284 }
285 }
286 }
287 }
288
289 processHistogram();
290 writeProbs();
291 }
292
293 void OHFrequencyMap::processHistogram() {
294 int atot = 0;
295 for (unsigned int i = 0; i < count_.size(); ++i)
296 atot += count_[i];
297
298 for (unsigned int i = 0; i < count_.size(); ++i) {
299 histogram_[i] = double(count_[i] / double(atot));
300 }
301 }
302
303 void OHFrequencyMap::writeProbs() {
304 std::ofstream rdfStream(outputFilename_.c_str());
305 if (rdfStream.is_open()) {
306 rdfStream << "#OHFrequencyMap\n";
307 rdfStream << "#nFrames:\t" << nProcessed_ << "\n";
308 rdfStream << "#selection1: (" << selectionScript1_ << ")";
309 rdfStream << "\n";
310 rdfStream << "#nu\tp(nu))\n";
311 for (unsigned int i = 0; i < histogram_.size(); ++i) {
312 RealType freq = minFreq_ + (RealType)(i) * (maxFreq_ - minFreq_) /
313 (RealType)histogram_.size();
314 rdfStream << freq << "\t" << histogram_[i] << "\n";
315 }
316
317 } else {
318 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
319 "OHFrequencyMap: unable to open %s\n",
320 outputFilename_.c_str());
321 painCave.isFatal = 1;
322 simError();
323 }
324
325 rdfStream.close();
326 }
327
328} // namespace OpenMD
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Definition SimInfo.hpp:96
void normalize()
Normalizes this vector in place.
Definition Vector.hpp:406
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.
std::string getPrefix(const std::string &str)