OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
SFG.hpp
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
32#ifndef APPLICATIONS_DYNAMICPROPS_SFG_HPP
33#define APPLICATIONS_DYNAMICPROPS_SFG_HPP
34
35#include <complex>
36#include <map>
37#include <string>
38#include <tuple>
39#include <utility>
40#include <vector>
41
42#include "applications/dynamicProps/TimeCorrFunc.hpp"
47#include "math/Vector3.hpp"
48
49namespace OpenMD {
50
51 /**
52 * Computes the vibrational SFG susceptibility spectrum Im χ²(ω)
53 * in the OH-stretch region using the exciton Hamiltonian approach with
54 * an ADIABATIC quantum propagator.
55 *
56 * Algorithm mirrors the MultiSpec code (Kananenka group, exc.cpp):
57 *
58 * C(t) = α_pq(t)ᵀ · F(t,0) · μ_r(0)
59 *
60 * where F(t,0) is the time-ordered adiabatic propagator:
61 *
62 * F(t+dt, 0) = exp(i H(t) dt / ℏ) · F(t, 0)
63 *
64 * exp(i H dt / ℏ) is evaluated by diagonalizing H at each step:
65 * exp(i H dt / ℏ) = V · diag(exp(i ω_k dt / ℏ)) · V†
66 * where V contains eigenvectors of H as columns.
67 *
68 * All quantities are kept in the LOCAL (site) basis throughout.
69 * The trajectory is divided into navg windows of length nTimeBins_ frames,
70 * separated by nSep_ frames, starting at nStart_; these are all populated
71 * by the base-class setWindowingParameters() and preCorrelate(). We override
72 * correlation() (which is virtual) to implement the adiabatic loop directly,
73 * and computeProperty1() to cache per-frame data during the preCorrelate scan.
74 *
75 * Spectroscopic maps (Auer & Skinner 2008 for SPC/E;
76 * Gruenbaum et al. 2013 for TIP4P):
77 * w10_ : ω₁₀(E) = a0 + a1·E + a2·E² [cm⁻¹; E in a.u.]
78 * muPrime_: μ′(E) = m0 + m1·E + m2·E² [a.u., dipole deriv.]
79 * x10_ : x₁₀(ω) = x0 + x1·ω₁₀ [a.u., coord. matrix el.]
80 * p10_ : p₁₀(ω) = p0 + p1·ω₁₀ [a.u., momentum matrix el.]
81 * Transition dipole |μ₁₀| = μ′·x₁₀ (converted to Debye).
82 * wintra_: ω^intra = [k0+k1·(Eⱼ+Eₖ)]·xⱼ·xₖ + kp·pⱼ·pₖ [cm⁻¹]
83 * alphaMap_: (α_par, α_perp) [Ų amu⁻⁰·⁵]
84 *
85 * Supported polarization combinations (interface normal = z):
86 * ssp : Im C_yyz = α_yy(t)ᵀ · F(t,0) · μ_z(0)
87 * ppp : Im [−0.5·C_xxz − 0.5·C_yyz + C_zzz] (near-normal incidence)
88 * sps : Im C_yzy = α_yz(t)ᵀ · F(t,0) · μ_y(0)
89 */
90 class SFG : public SystemACF<RealType> {
91 public:
92 SFG(SimInfo* info, const std::string& filename, const std::string& sele1,
93 const std::string& sele2, const std::string& polarization = "ssp",
94 int privilegedAxis = 2,
95 RealType t_apod = 0.0, RealType t_zerofill = 0.0,
96 RealType fc = 25.0);
97
98 virtual ~SFG();
99
100 protected:
101 // computeProperty1 is called by preCorrelate() for every frame.
102 // We use it to build and cache the FrameData for that frame.
103 void computeProperty1(int frame) override;
104
105 // calcCorrVal satisfies the pure-virtual requirement but is never
106 // called because we override correlation() to bypass correlateFrames().
107 RealType calcCorrVal(int frame1, int frame2) override { return 0.0; }
108
109 // Override the correlation loop with the adiabatic windowed propagation.
110 // Called by the base doCorrelate() after preCorrelate() has read all
111 // frames and populated nTimeBins_, dtMean_, nStart_, nSep_, nStride_,
112 // navg_, allFrames_, etc.
113 void correlation() override;
114
115 void writeCorrelate() override;
116
117 private:
118 // -----------------------------------------------------------------------
119 // Spectroscopic map tables, keyed by H atom type name.
120 //
121 // w10_ : (a0,a1,a2) ω₁₀(E) = a0 + a1·E + a2·E² [cm⁻¹]
122 // muPrime_ : (m0,m1,m2) μ′(E) = m0 + m1·E + m2·E² [a.u.]
123 // Dipole derivative along OH bond.
124 // x10_ : (x0,x1) x₁₀(ω) = x0 + x1·ω₁₀ [a.u.]
125 // Coordinate matrix element. |μ₁₀| = μ′·x₁₀.
126 // p10_ : (p0,p1) p₁₀(ω) = p0 + p1·ω₁₀ [a.u.]
127 // Momentum matrix element.
128 // wintra_ : (k0,k1,kinetic_coeff)
129 // ω^intra = [k0 + k1·(Eⱼ+Eₖ)]·xⱼ·xₖ
130 // + kinetic_coeff·pⱼ·pₖ [cm⁻¹]
131 // (kinetic_coeff < 0; includes −cosφ/m_O factor)
132 // alphaMap_: (α_par, α_perp) [Ų amu⁻⁰·⁵]
133 // tdcLoc_ : distance from O along OH bond for TDC location [Å]
134 // (Auer 2008: 0.58 Å for SPC/E; Gruenbaum 2013: 0.67 Å for TIP4P)
135 //
136 // Bend maps (Ni & Skinner JCP 143, 014502 (2015), for H2O):
137 // wb01_ : (b0, b1) ω_{0→1}(E_b) = b0 + b1·E_b [cm⁻¹]
138 // wb12_ : (c0, c1) ω_{1→2}(E_b) = c0 + c1·E_b [cm⁻¹]
139 // ω₂δ on Hamiltonian diagonal = ω_{0→1} + ω_{1→2}.
140 // E_b is the field projected on the HOH bisector (computed in extractFrame).
141 // -----------------------------------------------------------------------
142 std::map<std::string, std::tuple<RealType,RealType,RealType>> w10_;
143 std::map<std::string, std::tuple<RealType,RealType,RealType>> muPrime_;
144 std::map<std::string, std::pair<RealType,RealType>> x10_;
145 std::map<std::string, std::pair<RealType,RealType>> p10_;
146 std::map<std::string, std::tuple<RealType,RealType,RealType>> wintra_;
147 std::map<std::string, std::pair<RealType,RealType>> alphaMap_;
148 std::map<std::string, RealType> tdcLoc_;
149
150 // Bend maps keyed by water-model name (e.g. "TIP4P", "SPCE", "TIP4P-Ice")
151 std::map<std::string, std::pair<RealType,RealType>> wb01_;
152 std::map<std::string, std::pair<RealType,RealType>> wb12_;
153
154 // Fermi coupling [cm⁻¹] between OH stretch and HOH bend overtone
155 // on the same molecule. Default 25 cm⁻¹ following MultiSpec / Ni 2015.
156 // If 0, Fermi resonance is effectively disabled.
157 RealType fc_ {25.0};
158
159 // True if a bend map is available for the water model(s) selected.
160 // Set during the first extractFrame() call.
161 bool useFermi_ {false};
162
163 // Applied external field [kcal mol⁻¹ Å⁻¹ e⁻¹], pre-converted in ctor
164 Vector3d EF_;
165
166 // Whether the dump already contains electric fields
167 bool dumpHasElectricFields_;
168 ForceManager* forceMan_ {nullptr};
169
170 // Polarization combination label ("ssp", "ppp", "sps")
171 std::string polarization_;
172
173 // Interface normal axis: 0=x, 1=y, 2=z (default 2).
174 // s1_ and s2_ are the surface-parallel axes in cyclic order:
175 // n=2 (z): s1=0 (x), s2=1 (y) [standard water/vapor interface]
176 // n=0 (x): s1=1 (y), s2=2 (z)
177 // n=1 (y): s1=2 (z), s2=0 (x)
178 int privilegedAxis_ {2};
179 int s1_ {0};
180 int s2_ {1};
181
182 // -----------------------------------------------------------------------
183 // Per-frame data in the LOCAL (site) basis.
184 //
185 // When Fermi resonance is enabled, the Hamiltonian and chromophore arrays
186 // include bend-overtone entries appended after the OH stretches:
187 // indices [0 .. N_stretch-1] : OH-stretch chromophores
188 // indices [N_stretch .. N_stretch+N_bend-1] : HOH bend overtones
189 //
190 // Total chromophore count is fd.N = N_stretch + N_bend.
191 // The bend overtones have zero transition dipole and polarizability
192 // (their intensity is borrowed from the stretches via Fermi mixing).
193 // -----------------------------------------------------------------------
194 struct FrameData {
195 int N {0}; // total chromophores (stretches+bends)
196 int nStretch {0}; // number of OH stretches
197 int nBend {0}; // number of bend overtones
198 DynamicRectMatrix<RealType> H; // NxN exciton Hamiltonian [cm-1]
199 std::vector<Vector3d> mu; // transition dipole vectors [D]
200 std::vector<Mat3x3d> alpha; // Raman polarizability tensors
201 std::vector<int> globalIDs; // global H-atom index per chromophore
202 // (for bend overtones: O atom global ID)
203 std::map<int,int> idToIndex; // reverse map: globalID -> local index
204 };
205
206 // All frames from the dump, populated by computeProperty1() during
207 // preCorrelate(). Indexed by frame number [0 .. nFrames_-1].
208 std::vector<FrameData> allFrames_;
209
210 // Accumulated TCF (complex; length nTimeBins_; averaged over navg_ windows)
211 std::vector<std::complex<double>> tcf_ssp_;
212 std::vector<std::complex<double>> tcf_ppp_;
213 std::vector<std::complex<double>> tcf_sps_;
214
215 // T1 vibrational relaxation time [ps]; 0 = no relaxation
216 RealType T1_ps_ {0.0};
217
218 // Apodization time constant [ps].
219 // TCF is multiplied by exp(-t/t_apod_ps) before FFT.
220 // 0 = no apodization. Adds ~33.36/(pi*t_apod_ps) cm-1 of Lorentzian
221 // broadening; a value of ~T_corr/3 is a good starting point.
222 RealType t_apod_ps_ {0.0};
223
224 // Zero-filling duration [ps].
225 // The apodized TCF is padded with zeros out to
226 // (T_corr + t_zerofill_ps) before FFT, interpolating the spectrum
227 // onto a finer frequency grid without adding new information.
228 // 0 = no zero-filling. A value of 2-4x T_corr is typical.
229 RealType t_zerofill_ps_ {0.0};
230
231 // Average OH frequency computed from actual per-frame diagonal entries
232 // across all frames during preCorrelate(). Finalized at the start of
233 // correlation() and subtracted from every stored H diagonal before
234 // propagation. Restored on the frequency axis in writeCorrelate().
235 RealType wAvg_ {0.0};
236 RealType wAvgSum_ {0.0}; // running sum of all ω₁₀ values seen
237 long wAvgCount_ {0}; // total number of ω₁₀ values accumulated
238
239 // Frame interval [ps], set at start of correlation() and reused
240 // in writeCorrelate()
241 RealType dt_ps_ {0.001};
242
243 // -----------------------------------------------------------------------
244 // Private helpers
245 // -----------------------------------------------------------------------
246
247 /**
248 * Extract spectroscopic-map quantities from currentSnapshot_ and build
249 * a FrameData. Also fills ohPos and molIndex needed for buildHamiltonian.
250 */
251 FrameData extractFrame(std::vector<Vector3d>& ohPos,
252 std::vector<int>& molIndex,
253 std::vector<RealType>& intraJ);
254
255 /**
256 * Fill fd.H with the N×N exciton Hamiltonian.
257 * Diagonal: local ω₁₀. Off-diagonal: intraJ (same mol) or TDC.
258 */
259 void buildHamiltonian(FrameData& fd,
260 const std::vector<Vector3d>& ohPos,
261 const std::vector<int>& molIndex,
262 const std::vector<RealType>& intraJ);
263
264 /**
265 * Propagate F by one time step:
266 * F ← exp(i H dt/ℏ) · F (adiabatic step)
267 *
268 * @param F N²-element complex vector (row-major N×N)
269 * @param H N×N real symmetric Hamiltonian [cm⁻¹]
270 * @param dt_ps time step [ps]
271 * @param N system size
272 */
273 void propagateF(std::vector<std::complex<double>>& F,
275 RealType dt_ps, int N);
276
277 /**
278 * Accumulate one lag point into tcf_ssp_, tcf_ppp_, tcf_sps_.
279 *
280 * For each polarization element pqr:
281 * C_pqr(t) += Σᵢ α_pq,i(t) * [F · μ_r(0)]ᵢ × exp(−t/2T1)
282 *
283 * @param tt lag index [0 .. ncor_-1]
284 * @param F current N×N propagator (row-major complex vector)
285 * @param fd0 FrameData at t=0 (μ₀ components)
286 * @param fdt FrameData at t (α components)
287 * @param N system size
288 * @param exptc T1 decay: exp(−tt·dt_ps / (2·T1_ps_)); 1.0 if T1=0
289 */
290 void accumulateTCF(int tt,
291 const std::vector<std::complex<double>>& F,
292 const FrameData& fd0,
293 const FrameData& fdt,
294 int N, RealType exptc,
295 std::vector<std::complex<double>>& tgt_ssp,
296 std::vector<std::complex<double>>& tgt_ppp,
297 std::vector<std::complex<double>>& tgt_sps);
298
299 /**
300 * TDC coupling between sites i and j (MultiSpec convention).
301 * J = AU_TO_WN × geom × (μ′·x₁₀)ᵢ × (μ′·x₁₀)ⱼ / r_bohr³
302 * where geom = (eᵢ·eⱼ − 3(eᵢ·r̂)(eⱼ·r̂)) using unit OH vectors.
303 */
304 RealType tdcCoupling(const Vector3d& r_ij,
305 const Vector3d& e_i, const Vector3d& e_j,
306 RealType mx_i, RealType mx_j);
307
308 /**
309 * Build a FrameData containing only the atoms in refIDs, in that order,
310 * drawn from src. Atoms absent from src get zero μ/α and their diagonal
311 * H entry is taken from refFreqs. Returns false if more than half the
312 * reference atoms are missing.
313 */
314 bool remapFrame(const FrameData& src,
315 const std::vector<int>& refIDs,
316 const std::vector<RealType>& refFreqs,
317 FrameData& out,
318 int refNStretch,
319 int refNBend);
320
321 /**
322 * Bond-polarizability tensor for one OH bond.
323 * α = [α_perp·I + (α_par − α_perp)·ê⊗ê] × (x10/x10_gas)
324 * The x10/x10_gas factor captures the field-dependent coordinate
325 * matrix element following MultiSpec's trPol() convention.
326 */
327 Mat3x3d bondPolarizability(const Vector3d& ohUnit,
328 RealType alpha_par, RealType alpha_perp,
329 RealType x10, RealType x10_gas);
330 };
331
332} // namespace OpenMD
333#endif
Rectangular matrix class with contiguous flat storage.
ForceManager is responsible for calculating both the short range (bonded) interactions and long range...
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Definition SimInfo.hpp:96
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.