37#include "applications/staticProps/SFGTimeAvg.hpp"
46#include "math/Eigenvalue.hpp"
48#include "utils/simError.h"
50#if defined(HAVE_LAPACK)
52 void dsyevd_(
const char* jobz,
const char* uplo,
const int* n,
53 double* a,
const int* lda,
double* w,
54 double* work,
const int* lwork,
55 int* iwork,
const int* liwork,
int* info);
61 SFGTimeAvg::SFGTimeAvg(
SimInfo* info,
const std::string& filename,
62 const std::string& sele1,
int nbins,
63 const std::string& polarization,
int privilegedAxis,
64 RealType gamma, RealType fc) :
66 selectionScript1_(sele1), seleMan1_(info_), evaluator1_(info_),
67 polarization_(polarization),
68 privilegedAxis_(privilegedAxis),
69 s1_((privilegedAxis + 1) % 3),
70 s2_((privilegedAxis + 2) % 3),
71 gamma_(gamma), fc_(fc) {
73 setOutputName(
getPrefix(filename) +
".sfgta");
75 dumpHasElectricFields_ = info_->getSimParams()->getOutputElectricField();
77 if (!dumpHasElectricFields_) {
78 int atomStorageLayout = info_->getAtomStorageLayout();
79 int rigidBodyStorageLayout = info->getRigidBodyStorageLayout();
80 int cutoffGroupStorageLayout = info->getCutoffGroupStorageLayout();
82 atomStorageLayout |= DataStorage::dslElectricField;
83 rigidBodyStorageLayout |= DataStorage::dslElectricField;
84 info_->setAtomStorageLayout(atomStorageLayout);
85 info_->setRigidBodyStorageLayout(rigidBodyStorageLayout);
86 info_->setCutoffGroupStorageLayout(cutoffGroupStorageLayout);
88 info_->setSnapshotManager(
new SimSnapshotManager(info_, atomStorageLayout,
89 rigidBodyStorageLayout,
90 cutoffGroupStorageLayout));
92 info_->getSimParams()->setOutputElectricField(
true);
94 evaluator1_.loadScriptString(sele1);
95 if (!evaluator1_.isDynamic()) {
96 seleMan1_.setSelectionSet(evaluator1_.evaluate());
105 w10_[
"H_SPCE"] = std::make_tuple(3761.6, -5060.4, -86225.0);
106 muPrime_[
"H_SPCE"] = std::make_tuple(0.1333, 14.17, 0.0);
107 x10_[
"H_SPCE"] = std::make_pair(0.1934, -1.75e-5);
108 p10_[
"H_SPCE"] = std::make_pair(1.611, 5.893e-4);
109 wintra_[
"H_SPCE"] = std::make_tuple(-1789.0, 23852.0, -1.966);
110 alphaMap_[
"H_SPCE"] = std::make_pair(0.185, 0.033);
111 tdcLoc_[
"H_SPCE"] = 0.58;
114 w10_[
"H_TIP4P"] = std::make_tuple(3760.2, -3541.7, -152677.0);
115 muPrime_[
"H_TIP4P"] = std::make_tuple(0.1646, 11.39, 63.41);
116 x10_[
"H_TIP4P"] = std::make_pair(0.19285, -1.7261e-5);
117 p10_[
"H_TIP4P"] = std::make_pair(1.6466, 5.7692e-4);
118 wintra_[
"H_TIP4P"] = std::make_tuple(-1361.0, 27165.0, -1.887);
119 alphaMap_[
"H_TIP4P"] = std::make_pair(0.185, 0.033);
120 tdcLoc_[
"H_TIP4P"] = 0.67;
123 w10_[
"H_TIP4P-Ice"] = w10_[
"H_TIP4P"];
124 muPrime_[
"H_TIP4P-Ice"] = muPrime_[
"H_TIP4P"];
125 x10_[
"H_TIP4P-Ice"] = x10_[
"H_TIP4P"];
126 p10_[
"H_TIP4P-Ice"] = p10_[
"H_TIP4P"];
127 wintra_[
"H_TIP4P-Ice"] = wintra_[
"H_TIP4P"];
128 alphaMap_[
"H_TIP4P-Ice"] = alphaMap_[
"H_TIP4P"];
129 tdcLoc_[
"H_TIP4P-Ice"] = tdcLoc_[
"H_TIP4P"];
132 wb01_[
"TIP4P"] = std::make_pair(1581.46, 2938.51);
133 wb12_[
"TIP4P"] = std::make_pair(1551.32, 3147.80);
134 wb01_[
"TIP4P-Ice"] = wb01_[
"TIP4P"];
135 wb12_[
"TIP4P-Ice"] = wb12_[
"TIP4P"];
136 wb01_[
"SPCE"] = wb01_[
"TIP4P"];
137 wb12_[
"SPCE"] = wb12_[
"TIP4P"];
141 std::vector<RealType> ef;
143 if (info_->getSimParams()->haveElectricField()) {
144 efSpec =
true; ef = info_->getSimParams()->getElectricField();
146 if (info_->getSimParams()->haveUniformField()) {
147 efSpec =
true; ef = info_->getSimParams()->getUniformField();
150 if (ef.size() != 3) {
151 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
152 "SFGTimeAvg: uniformField needs 3 parameters, %zu given.\n",
154 painCave.isFatal = 1; simError();
156 EF_.x() = ef[0]; EF_.y() = ef[1]; EF_.z() = ef[2];
163 void SFGTimeAvg::process() {
164 ForceManager* forceMan =
nullptr;
166 DumpReader reader(info_, dumpFilename_);
167 int nFrames = reader.getNFrames();
170 if (!dumpHasElectricFields_) {
171 forceMan =
new ForceManager(info_);
172 forceMan->setDoElectricField(
true);
173 forceMan->initialize();
176 std::fill(chi2_.begin(), chi2_.end(),
177 std::complex<double>(0.0, 0.0));
179 for (
int istep = 0; istep < nFrames; istep += step_) {
180 reader.readFrame(istep);
181 currentSnapshot_ = info_->getSnapshotManager()->getCurrentSnapshot();
183 if (!dumpHasElectricFields_) {
184 forceMan->setDoElectricField(
true);
185 forceMan->calcForces();
188 if (evaluator1_.isDynamic()) {
189 seleMan1_.setSelectionSet(evaluator1_.evaluate());
192 std::vector<Vector3d> ohPos;
193 std::vector<int> molIndex;
194 std::vector<RealType> intraJ;
195 FrameData fd = extractFrame(ohPos, molIndex, intraJ);
196 if (fd.N == 0)
continue;
198 buildHamiltonian(fd, ohPos, molIndex, intraJ);
203 if (forceMan)
delete forceMan;
206 double avgFree = (diagNFree_ > 0)
207 ? diagMuzFree_ /
static_cast<double>(diagNFree_) : 0.0;
208 double avgHB = (diagNHB_ > 0)
209 ? diagMuzHB_ /
static_cast<double>(diagNHB_) : 0.0;
210 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
211 "SFGTimeAvg orientation diagnostic:\n"
212 "\t<mu_z> dangling (freq>3650): %.5f [n=%ld]\n"
213 "\t<mu_z> H-bonded (freq<3450): %.5f [n=%ld]\n"
214 "\t(these should have OPPOSITE signs for a real interface)\n",
215 avgFree, diagNFree_, avgHB, diagNHB_);
216 painCave.isFatal = 0; painCave.severity = OPENMD_INFO; simError();
226 SFGTimeAvg::FrameData
227 SFGTimeAvg::extractFrame(std::vector<Vector3d>& ohPos,
228 std::vector<int>& molIndex,
229 std::vector<RealType>& intraJ) {
230 const RealType kcalToAU = 0.0008432975573;
231 const RealType chrgToKcal = 23.060548;
234 ohPos.clear(); molIndex.clear(); intraJ.clear();
236 std::vector<RealType> freqs;
237 std::vector<RealType> x10vals, p10vals, mxvals, intraJ_k0, intraJ_kp;
241 Vector3d ohUnit1, ohUnit2;
242 Vector3d Efield1, Efield2;
243 RealType rOH1 = 0.0, rOH2 = 0.0;
245 std::string modelName;
247 std::map<int, MolData> molMap;
251 for (mol = seleMan1_.beginSelectedMolecule(ii); mol !=
nullptr;
252 mol = seleMan1_.nextSelectedMolecule(ii)) {
254 int molID = mol->getGlobalIndex();
259 std::vector<Atom*> hAtoms;
260 Molecule::AtomIterator ai;
261 for (Atom* atom = mol->beginAtom(ai); atom !=
nullptr;
262 atom = mol->nextAtom(ai)) {
263 const std::string& aname = atom->getAtomType()->getName();
264 if (aname[0] ==
'O' && !foundO) {
265 Opos = atom->getPos();
267 }
else if (w10_.find(aname) != w10_.end()) {
269 hAtoms.push_back(atom);
272 if (!foundO)
continue;
273 if (hAtoms.empty())
continue;
280 for (Atom* hAtom : hAtoms) {
281 std::string name = hAtom->getAtomType()->getName();
283 auto wIt = w10_.find(name);
284 auto mpIt = muPrime_.find(name);
285 if (wIt == w10_.end() || mpIt == muPrime_.end())
continue;
287 Vector3d Hpos = hAtom->getPos();
288 Vector3d rOH_mic = Hpos - Opos;
289 currentSnapshot_->wrapVector(rOH_mic);
290 RealType rOH_len = rOH_mic.
length();
291 Vector3d ohUnit = rOH_mic; ohUnit.
normalize();
293 Vector3d sE = hAtom->getElectricField();
294 sE += EF_ * chrgToKcal;
296 RealType E =
dot(ohUnit, sE);
298 auto [a0, a1, a2] = wIt->second;
299 RealType freq = a0 + a1*E + a2*E*E;
301 auto [m0, m1, m2] = mpIt->second;
302 RealType muPrime = m0 + m1*E + m2*E*E;
304 auto [x0, x1] = x10_.at(name);
305 RealType x10 = x0 + x1*freq;
307 auto [pv0, pv1] = p10_.at(name);
308 RealType p10 = pv0 + pv1*freq;
310 const RealType eaBohr_to_Debye = 2.5417464;
311 RealType muMag = muPrime * x10 * eaBohr_to_Debye;
313 RealType tdcDist = 0.5 * rOH_len;
314 auto tdcIt = tdcLoc_.find(name);
315 if (tdcIt != tdcLoc_.end()) tdcDist = tdcIt->second;
316 Vector3d dipolePos = Opos + tdcDist * ohUnit;
318 auto [apar, aperp] = alphaMap_.at(name);
319 RealType x10_gas = x10_.at(name).first;
322 fd.mu.push_back(muMag * ohUnit);
323 fd.alpha.push_back(bondPolarizability(ohUnit, apar, aperp,
328 double muz = (muMag * ohUnit)[privilegedAxis_];
329 if (freq > 3650.0) { diagMuzFree_ += muz; diagNFree_++; }
330 else if (freq < 3450.0) { diagMuzHB_ += muz; diagNHB_++; }
333 ohPos.push_back(dipolePos);
334 molIndex.push_back(molID);
335 freqs.push_back(freq);
336 x10vals.push_back(x10);
337 p10vals.push_back(p10);
338 mxvals.push_back(muPrime * x10);
340 auto [k0, k1, kp] = wintra_.at(name);
341 intraJ.push_back(k0 + k1*E);
342 intraJ_k0.push_back(k0);
343 intraJ_kp.push_back(kp);
345 MolData& md = molMap[molID];
347 md.modelName = mol->getType();
348 if (md.ohCount == 0) {
349 md.ohUnit1 = ohUnit; md.Efield1 = sE; md.rOH1 = rOH_len;
350 }
else if (md.ohCount == 1) {
351 md.ohUnit2 = ohUnit; md.Efield2 = sE; md.rOH2 = rOH_len;
358 fd.nStretch = nStretch;
363 for (
auto& [molID, md] : molMap) {
364 if (md.ohCount != 2)
continue;
365 auto bIt = wb01_.find(md.modelName);
366 if (bIt == wb01_.end())
continue;
367 auto cIt = wb12_.find(md.modelName);
368 if (cIt == wb12_.end())
continue;
370 Vector3d vp =
cross(md.ohUnit1, md.ohUnit2);
371 RealType vpLen = vp.length();
372 if (vpLen < 1.0e-9)
continue;
375 Vector3d ePerp1 =
cross(vp, md.ohUnit1);
376 Vector3d ePerp2 =
cross(md.ohUnit2, vp);
378 RealType Eb =
dot(ePerp1, md.Efield1) / md.rOH1
379 +
dot(ePerp2, md.Efield2) / md.rOH2;
381 auto [b0_10, b1_10] = bIt->second;
382 auto [b0_21, b1_21] = cIt->second;
383 RealType w_2d = (b0_10 + b1_10*Eb) + (b0_21 + b1_21*Eb);
386 fd.mu.push_back(V3Zero);
387 fd.alpha.push_back(Mat3x3d(0.0));
389 ohPos.push_back(md.Opos);
390 molIndex.push_back(molID);
391 freqs.push_back(w_2d);
392 x10vals.push_back(0.0);
393 p10vals.push_back(0.0);
394 mxvals.push_back(0.0);
395 intraJ.push_back(0.0);
396 intraJ_k0.push_back(0.0);
397 intraJ_kp.push_back(0.0);
403 useFermi_ = (nBend > 0);
407 for (
int i = 0; i < N; ++i)
408 fd.H(i, i) = freqs[i];
412 std::vector<RealType> packed;
413 packed.reserve(6 * N);
414 for (
int i = 0; i < N; ++i) {
415 packed.push_back(intraJ[i]);
416 packed.push_back(x10vals[i]);
417 packed.push_back(p10vals[i]);
418 packed.push_back(intraJ_k0[i]);
419 packed.push_back(intraJ_kp[i]);
420 packed.push_back(mxvals[i]);
422 intraJ = std::move(packed);
431 void SFGTimeAvg::buildHamiltonian(FrameData& fd,
432 const std::vector<Vector3d>& ohPos,
433 const std::vector<int>& molIndex,
434 const std::vector<RealType>& intraJ) {
437 const int nStr = fd.nStretch;
439 for (
int i = 0; i < N; ++i) {
440 bool i_is_bend = (i >= nStr);
441 for (
int j = i+1; j < N; ++j) {
442 bool j_is_bend = (j >= nStr);
444 if (molIndex[i] == molIndex[j]) {
445 if (!i_is_bend && !j_is_bend) {
446 RealType Ki = intraJ[6*i + 0];
447 RealType xi = intraJ[6*i + 1];
448 RealType pi = intraJ[6*i + 2];
449 RealType k0_val = intraJ[6*i + 3];
450 RealType kp_val = intraJ[6*i + 4];
451 RealType Kj = intraJ[6*j + 0];
452 RealType xj = intraJ[6*j + 1];
453 RealType pj = intraJ[6*j + 2];
454 J = (Ki + Kj - k0_val) * xi * xj + kp_val * pi * pj;
455 }
else if (i_is_bend != j_is_bend) {
459 Vector3d r_ij = ohPos[j] - ohPos[i];
460 currentSnapshot_->wrapVector(r_ij);
461 Vector3d e_i = fd.mu[i]; RealType m_i = e_i.
length();
462 if (m_i > 1.0e-12) e_i /= m_i;
463 Vector3d e_j = fd.mu[j]; RealType m_j = e_j.
length();
464 if (m_j > 1.0e-12) e_j /= m_j;
465 RealType mx_i = intraJ[6*i + 5];
466 RealType mx_j = intraJ[6*j + 5];
467 J = tdcCoupling(r_ij, e_i, e_j, mx_i, mx_j);
479 std::vector<double>& evals,
480 std::vector<double>& evecs) {
481 evals.assign(N, 0.0);
482 evecs.assign(N*N, 0.0);
484#if defined(HAVE_LAPACK)
485 std::vector<double> A(N*N);
488 for (
int i = 0; i < N*N; ++i) A[i] = static_cast<double>(src[i]);
491 int lwork = -1, liwork = -1, info = 0;
492 double wq = 0.0;
int wiq = 0;
493 dsyevd_(
"V",
"U", &N, A.data(), &N, evals.data(),
494 &wq, &lwork, &wiq, &liwork, &info);
495 lwork =
static_cast<int>(wq);
497 std::vector<double> work(std::max(1, lwork));
498 std::vector<int> iwork(std::max(1, liwork));
500 dsyevd_(
"V",
"U", &N, A.data(), &N, evals.data(),
501 work.data(), &lwork, iwork.data(), &liwork, &info);
505 for (
int i = 0; i < N; ++i)
506 for (
int a = 0; a < N; ++a)
507 evecs[i*N + a] = A[i + a*N];
510 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
511 "SFGTimeAvg::diagonalize: dsyevd info=%d; using JAMA.\n", info);
512 painCave.isFatal = 0; painCave.severity = OPENMD_WARNING; simError();
519 eig.getRealEigenvalues(ev);
521 for (
int a = 0; a < N; ++a) {
522 evals[a] =
static_cast<double>(ev[a]);
523 for (
int i = 0; i < N; ++i)
524 evecs[i*N + a] =
static_cast<double>(V(i, a));
532 void SFGTimeAvg::accumulateFrame(
const FrameData& fd) {
534 std::vector<double> evals, evecs;
535 diagonalize(fd.H, N, evals, evecs);
537 const double dOmega = (maxFreq_ - minFreq_) /
static_cast<double>(nBins_);
539 const double cutoff = 20.0 * gamma_;
540 const int halfWin =
static_cast<int>(std::ceil(cutoff / dOmega));
542 for (
int a = 0; a < N; ++a) {
543 double wa = evals[a];
544 if (wa < minFreq_ - cutoff || wa > maxFreq_ + cutoff)
continue;
549 double a_s1s1 = 0.0, a_s2s2 = 0.0, a_nn = 0.0;
550 double a_s1n = 0.0, a_s2n = 0.0;
551 for (
int i = 0; i < N; ++i) {
552 double v = evecs[i*N + a];
553 if (v == 0.0)
continue;
554 const Vector3d& mu = fd.mu[i];
555 const Mat3x3d& al = fd.alpha[i];
556 mu_n += v * mu[privilegedAxis_];
557 a_s1s1 += v * al(s1_, s1_);
558 a_s2s2 += v * al(s2_, s2_);
559 a_nn += v * al(privilegedAxis_, privilegedAxis_);
560 a_s1n += v * al(s1_, privilegedAxis_);
561 a_s2n += v * al(s2_, privilegedAxis_);
566 if (polarization_ ==
"ssp") {
568 amp = 0.5 * (a_s1s1 + a_s2s2) * mu_n;
569 }
else if (polarization_ ==
"ppp") {
570 amp = (-0.5*a_s1s1 - 0.5*a_s2s2 + a_nn) * mu_n;
571 }
else if (polarization_ ==
"sps") {
573 amp = 0.5 * (a_s1n + a_s2n) * mu_n;
575 amp = 0.5 * (a_s1s1 + a_s2s2) * mu_n;
585 int centerBin =
static_cast<int>((wa - minFreq_) / dOmega);
586 int lo = std::max(0, centerBin - halfWin);
587 int hi = std::min(
static_cast<int>(nBins_) - 1, centerBin + halfWin);
588 for (
int b = lo; b <= hi; ++b) {
589 double w = minFreq_ + (b + 0.5) * dOmega;
590 std::complex<double> denom(w - wa, -gamma_);
591 chi2_[b] += amp / denom;
599 void SFGTimeAvg::writeSpectrum() {
600 const double dOmega = (maxFreq_ - minFreq_) /
static_cast<double>(nBins_);
604 if (info_->getSimParams()->haveTargetTemp())
605 T = info_->getSimParams()->getTargetTemp();
606 const double kT_invcm = 0.6950356 *
static_cast<double>(T);
608 double invN = (nProcessed_ > 0)
609 ? 1.0 /
static_cast<double>(nProcessed_) : 1.0;
611 std::ofstream ofs(outputFilename_.c_str());
612 if (!ofs.is_open()) {
613 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
614 "SFGTimeAvg: cannot open %s\n", outputFilename_.c_str());
615 painCave.isFatal = 1; simError();
618 ofs <<
"# SFG spectrum (time-averaging approximation, "
619 << polarization_ <<
")\n";
620 ofs <<
"# selection: (" << selectionScript1_ <<
")\n";
621 ofs <<
"# nFrames = " << nProcessed_ <<
"\n";
622 ofs <<
"# T = " << T <<
" K, kT = " << kT_invcm <<
" cm-1\n";
623 ofs <<
"# Lorentzian half-width gamma = " << gamma_ <<
" cm-1\n";
624 ofs <<
"# Fermi coupling fc = " << fc_ <<
" cm-1"
625 << (useFermi_ ?
" (bend overtone included)" :
"") <<
"\n";
626 ofs <<
"#omega(cm-1)\tRe(chi2)\tIm(chi2)\t|chi2|^2\n";
628 for (
int b = 0; b < static_cast<int>(nBins_); ++b) {
629 double w = minFreq_ + (b + 0.5) * dOmega;
630 std::complex<double> c = chi2_[b] * invN;
632 double x = w / kT_invcm;
633 double qCorr = (x > 1.0e-6)
634 ? ((x < 50.0) ? x / (1.0 - std::exp(-x)) : x) : 1.0;
635 double re = c.real() * qCorr;
636 double im = c.imag() * qCorr;
638 ofs << w <<
"\t" << re <<
"\t" << im <<
"\t"
639 << re*re + im*im <<
"\n";
648 Mat3x3d SFGTimeAvg::bondPolarizability(
const Vector3d& ohUnit,
649 RealType alpha_par, RealType alpha_perp,
650 RealType x10, RealType x10_gas) {
651 RealType scale = (x10_gas > 0.0) ? x10 / x10_gas : 1.0;
652 RealType da = alpha_par - alpha_perp;
654 for (
int p = 0; p < 3; ++p) {
655 a(p, p) = (alpha_perp + da * ohUnit[p] * ohUnit[p]) * scale;
656 for (
int q = p+1; q < 3; ++q) {
657 a(p, q) = da * ohUnit[p] * ohUnit[q] * scale;
668 RealType SFGTimeAvg::tdcCoupling(
const Vector3d& r_ij,
669 const Vector3d& e_i,
const Vector3d& e_j,
670 RealType mx_i, RealType mx_j) {
671 RealType r_A = r_ij.length();
672 if (r_A < 1.0e-6)
return 0.0;
673 Vector3d rhat = r_ij / r_A;
674 const RealType A_to_bohr = 1.0 / 0.52917721067;
675 RealType r_bohr = r_A * A_to_bohr;
676 RealType r3 = r_bohr * r_bohr * r_bohr;
677 const RealType AU_TO_WN = 219474.6313705;
678 RealType geom =
dot(e_i, e_j) - 3.0 *
dot(e_i, rhat) *
dot(e_j, rhat);
679 return AU_TO_WN * geom * mx_i * mx_j / r3;
Computes eigenvalues and eigenvectors of a real (non-complex) matrix.
Rectangular matrix class with contiguous flat storage.
Real * getArrayPointer()
Returns a pointer to the contiguous internal storage (row-major).
Dynamically-sized vector class.
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
void normalize()
Normalizes this vector in place.
Real length() const
Returns the length of this vector.
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
Vector3< Real > cross(const Vector3< Real > &v1, const Vector3< Real > &v2)
Returns the cross product of two Vectors.
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.
std::string getPrefix(const std::string &str)