OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
Light.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
49
50#ifdef IS_MPI
51#include <mpi.h>
52#endif
53
54#include "brains/ForceModifier.hpp"
55#include "nonbonded/NonBondedInteraction.hpp"
57#include "types/FixedChargeAdapter.hpp"
58#include "types/FluctuatingChargeAdapter.hpp"
59#include "types/MultipoleAdapter.hpp"
60#include "utils/Constants.hpp"
61
62namespace OpenMD::Perturbations {
63 Light::Light(SimInfo* info) :
64 ForceModifier {info}, initialized {false}, doLight {false},
65 doParticlePot {false}, info_(info) {
66 lightParams = info_->getSimParams()->getLightParameters();
67 }
68
69 void Light::initialize() {
70 bool haveE0 = false;
71 bool haveDirection = false;
72 bool haveFrequency = false;
73 bool havePolarization = false;
74
75 if (lightParams->haveWaveVector()) {
76 std::vector<RealType> k = lightParams->getWaveVector();
77 // wave vectors are input in inverse angstroms, so no unit conversion:
78 k_.x() = k[0];
79 k_.y() = k[1];
80 k_.z() = k[2];
81 kmag_ = k_.length();
82 lambda_ = 2.0 * Constants::PI / kmag_;
83 omega_ = 2.0 * Constants::PI * Constants::c / lambda_;
84 haveFrequency = true;
85 khat_ = k_;
86 khat_.normalize();
87 haveDirection = true;
88 }
89
90 if (lightParams->havePropagationDirection()) {
91 if (haveDirection) {
92 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
93 "light: please specify either waveVector or "
94 "propagationDirection, but not both.\n");
95 painCave.isFatal = 1;
96 simError();
97 }
98 std::vector<RealType> pd = lightParams->getPropagationDirection();
99 khat_.x() = pd[0];
100 khat_.y() = pd[1];
101 khat_.z() = pd[2];
102 khat_.normalize();
103 haveDirection = true;
104 }
105
106 if (lightParams->haveWavelength()) {
107 if (haveFrequency) {
108 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
109 "light: please specify one of: waveVector, wavelength, or"
110 "frequency (but only one of these).\n");
111 painCave.isFatal = 1;
112 simError();
113 }
114 // wavelengths are entered in nm to work with experimentalists.
115 // Convert to angstroms:
116 lambda_ = lightParams->getWavelength() * 10.0;
117 omega_ = 2.0 * Constants::PI * Constants::c / lambda_;
118 kmag_ = 2.0 * Constants::PI / lambda_;
119 haveFrequency = true;
120 }
121
122 if (lightParams->haveFrequency()) {
123 if (haveFrequency) {
124 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
125 "light: please specify one of: waveVector, wavelength, or"
126 "frequency (but only one of these).\n");
127 painCave.isFatal = 1;
128 simError();
129 }
130 // frequencies are entered in Hz to work with experimentalists.
131 // Convert to fs^-1
132 omega_ = lightParams->getFrequency() * 1.0e-15;
133 lambda_ = 2.0 * Constants::PI * Constants::c / omega_;
134 kmag_ = 2.0 * Constants::PI / lambda_;
135 haveFrequency = true;
136 }
137
138 if (haveFrequency && haveDirection) { k_ = khat_ * kmag_; }
139
140 if (lightParams->haveIntensity()) {
141 RealType intense = lightParams->getIntensity();
142 // intensities are input in W/cm^2
143 intense *= 1.439326e-11;
144 E0_ = std::sqrt(2.0 * intense / (Constants::epsilon0 * Constants::c));
145 // E0 now has units of kcal/mol e^-1 Angstroms^-1
146 haveE0 = true;
147 }
148
149 // Determine Polarization Type
150 jones_.clear();
151 jones_.reserve(2);
152 std::map<std::string, LightPolarization> stringToPolarization;
153
154 stringToPolarization["X"] = lightX;
155 stringToPolarization["Y"] = lightY;
156 stringToPolarization["+"] = lightPlus;
157 stringToPolarization["-"] = lightMinus;
158
159 if (lightParams->havePolarization()) {
160 std::string lpl = lightParams->getPolarization();
161 LightPolarization lp = stringToPolarization.find(lpl)->second;
162 switch (lp) {
163 case lightX:
164 jones_[0] = {1.0, 0.0};
165 jones_[1] = {0.0, 0.0};
166 havePolarization = true;
167 break;
168 case lightY:
169 jones_[0] = {0.0, 0.0};
170 jones_[1] = {1.0, 0.0};
171 havePolarization = true;
172 break;
173 case lightPlus:
174 jones_[0] = {1.0, 0.0};
175 jones_[1] = {0.0, 1.0};
176 havePolarization = true;
177 break;
178 case lightMinus:
179 jones_[0] = {1.0, 0.0};
180 jones_[1] = {0.0, -1.0};
181 havePolarization = true;
182 break;
183 default:
184 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
185 "Light: Unknown polarization type\n");
186 painCave.isFatal = 1;
187 painCave.severity = OPENMD_ERROR;
188 simError();
189 break;
190 }
191
192 } else {
193 std::string allowedPolarizations;
194 int currentLineLength = 0;
195
196 for (std::map<std::string, LightPolarization>::iterator polStrIter =
197 stringToPolarization.begin();
198 polStrIter != stringToPolarization.end(); ++polStrIter) {
199 allowedPolarizations += polStrIter->first + ", ";
200 currentLineLength += polStrIter->first.length() + 2;
201
202 if (currentLineLength >= 50) {
203 allowedPolarizations += "\n\t\t";
204 currentLineLength = 0;
205 }
206 }
207
208 allowedPolarizations.erase(allowedPolarizations.length() - 2, 2);
209
210 snprintf(
211 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
212 "Light: No polarization was set in the omd file. This parameter\n"
213 "\tmust be set to use Light, and can take any of these values:\n"
214 "\t\t%s.\n",
215 allowedPolarizations.c_str());
216 painCave.isFatal = 1;
217 painCave.severity = OPENMD_ERROR;
218 simError();
219 }
220
221 if (haveE0 && haveDirection && haveFrequency && havePolarization) {
222 doLight = true;
223 } else {
224 if (!haveDirection) {
225 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
226 "Light: could not determine direction of propagation.\n");
227 painCave.isFatal = 1;
228 simError();
229 }
230 if (!haveE0) {
231 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
232 "Light: intensity not specified.\n");
233 painCave.isFatal = 1;
234 simError();
235 }
236 if (!haveFrequency) {
237 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
238 "Light: could not determine frequency or wavelength.\n");
239 painCave.isFatal = 1;
240 simError();
241 }
242 if (!havePolarization) {
243 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
244 "Light: polarization not specifieid.\n");
245 painCave.isFatal = 1;
246 simError();
247 }
248 }
249
250 int storageLayout_ = info_->getSnapshotManager()->getAtomStorageLayout();
251 if (storageLayout_ & DataStorage::dslParticlePot) doParticlePot = true;
252
253 // Relatively simple Euler angles between khat_ and lab frame:
254
255 RealType psi = 0.0;
256 RealType theta =
257 acos(std::min((RealType)1.0, std::max((RealType)-1.0, khat_[2])));
258 RealType phi = std::atan2(-khat_[1], khat_[0]);
259
260 if (phi < 0) phi += 2.0 * Constants::PI;
261
262 A_.setupRotMat(phi, theta, psi);
263 Ainv_ = A_.inverse();
264
265 initialized = true;
266 }
267
268 void Light::modifyForces() {
269 if (!initialized) initialize();
270
271 SimInfo::MoleculeIterator i;
272 Molecule::AtomIterator j;
273 Molecule* mol;
274 Atom* atom;
275 AtomType* atype;
276 potVec longRangePotential(0.0);
277 int l, m, n;
278 RealType C {}, U {}, fPot {};
279 Vector3d r {}, rp {}, v {}, f {}, trq {}, D {}, J {}, av {};
280 Vector3d EFk {}, EF {}, BF {};
281 Mat3x3d I {};
282
283 bool isCharge;
284
285 if (doLight) {
286 RealType t = info_->getSnapshotManager()->getCurrentSnapshot()->getTime();
287 U = 0.0;
288 fPot = 0.0;
289
290 for (mol = info_->beginMolecule(i); mol != NULL;
291 mol = info_->nextMolecule(i)) {
292 for (atom = mol->beginAtom(j); atom != NULL; atom = mol->nextAtom(j)) {
293 isCharge = false;
294 C = 0.0;
295
296 atype = atom->getAtomType();
297
298 // We are not wrapping coordinates for light interactions:
299 r = atom->getPos();
300 v = atom->getVel();
301
302 rp = A_ * r; // atom's position in frame of light propagation
303
304 // e^{ i (k*z - omega * t) } is the main oscillatory component:
305 std::complex<RealType> argument(0.0, kmag_ * rp.z() - omega_ * t);
306 std::complex<RealType> Ex = E0_ * jones_[0] * std::exp(argument);
307 std::complex<RealType> Ey = E0_ * jones_[1] * std::exp(argument);
308
309 EFk.x() = Ex.real();
310 EFk.y() = Ey.real();
311 EFk.z() = 0.0;
312
313 EF = Ainv_ * EFk; // electric field rotated back into lab coordinates
314
315 // The magnetic field (BF) is perpendicular to both electric field
316 // and light propagation direction:
317
318 BF = cross(EF, khat_) / Constants::c;
319
320 atom->addElectricField(EF);
321
322 FixedChargeAdapter fca = FixedChargeAdapter(atype);
323 if (fca.isFixedCharge()) {
324 isCharge = true;
325 C = fca.getCharge();
326 }
327
328 FluctuatingChargeAdapter fqa = FluctuatingChargeAdapter(atype);
329 if (fqa.isFluctuatingCharge()) {
330 isCharge = true;
331 C += atom->getFlucQPos();
332 atom->addFlucQFrc(dot(r, EF));
333 }
334
335 if (isCharge) {
336 f = C * (EF + cross(v, BF));
337 atom->addFrc(f);
338 U = -dot(r, f);
339 if (doParticlePot) { atom->addParticlePot(U); }
340 fPot += U;
341 }
342
343 MultipoleAdapter ma = MultipoleAdapter(atype);
344 if (ma.isDipole()) {
345 D = atom->getDipole() * Constants::dipoleConvert;
346
347 trq += cross(D, EF) + cross(D, cross(v, BF));
348
349 atom->addTrq(trq);
350
351 J = atom->getJ();
352 I = atom->getI();
353 if (atom->isLinear()) {
354 l = atom->linearAxis();
355 m = (l + 1) % 3;
356 n = (l + 2) % 3;
357 av[l] = 0;
358 av[m] = J[m] / I(m, m);
359 av[n] = J[n] / I(n, n);
360 } else {
361 av = I.inverse() * J;
362 }
363
364 f = cross(cross(av, D), BF);
365 atom->addFrc(f);
366
367 U = -dot(D, EF);
368 if (doParticlePot) { atom->addParticlePot(U); }
369 fPot += U;
370 }
371 }
372 }
373
374#ifdef IS_MPI
375 MPI_Allreduce(MPI_IN_PLACE, &fPot, 1, MPI_REALTYPE, MPI_SUM,
376 MPI_COMM_WORLD);
377#endif
378
379 Snapshot* snap = info_->getSnapshotManager()->getCurrentSnapshot();
380 longRangePotential = snap->getLongRangePotentials();
381 longRangePotential[ELECTROSTATIC_FAMILY] += fPot;
382 snap->setLongRangePotentials(longRangePotential);
383 }
384 }
385} // namespace OpenMD::Perturbations
Rotating Electric Field perturbation.
Vector3< Real > cross(const Vector3< Real > &v1, const Vector3< Real > &v2)
Returns the cross product of two Vectors.
Definition Vector3.hpp:139
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.
@ ELECTROSTATIC_FAMILY
Coulombic and point-multipole interactions.