OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
nanoparticleBuilder.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 <config.h>
49
50#include <algorithm>
51#include <cmath>
52#include <cstdio>
53#include <cstdlib>
54#include <cstring>
55#include <fstream>
56#include <iostream>
57#include <map>
58#include <random>
59#include <string>
60
61#include "brains/Register.hpp"
62#include "brains/SimCreator.hpp"
63#include "brains/SimInfo.hpp"
64#include "io/DumpWriter.hpp"
65#include "lattice/Lattice.hpp"
68#include "math/Vector3.hpp"
70#include "shapedLatticeSpherical.hpp"
71#include "utils/MoLocator.hpp"
72#include "utils/StringUtils.hpp"
73
74using namespace std;
75using namespace OpenMD;
76void createMdFile(const std::string& oldMdFileName,
77 const std::string& newMdFileName, std::vector<int> numMol);
78
79int main(int argc, char* argv[]) {
81
82 gengetopt_args_info args_info;
83 std::string latticeType;
84 std::string inputFileName;
85 std::string outputFileName;
86 MoLocator* locator;
87 int nComponents;
88 double latticeConstant;
89 RealType particleRadius;
90 Mat3x3d hmat;
91 DumpWriter* writer;
92
93 // Parse Command Line Arguments
94 if (cmdline_parser(argc, argv, &args_info) != 0) exit(1);
95
96 /* get lattice type */
97 latticeType = "FCC";
98
99 /* get input file name */
100 if (args_info.inputs_num)
101 inputFileName = args_info.inputs[0];
102 else {
103 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
104 "No input .omd file name was specified "
105 "on the command line");
106 painCave.isFatal = 1;
108 simError();
109 }
110
111 /* parse md file and set up the system */
112 SimCreator oldCreator;
113 SimInfo* oldInfo = oldCreator.createSim(inputFileName, false);
114
115 latticeConstant = args_info.latticeConstant_arg;
116 particleRadius = args_info.radius_arg;
117 Globals* simParams = oldInfo->getSimParams();
118
119 /* Create nanoparticle */
120 shapedLatticeSpherical nanoParticle(latticeConstant, latticeType,
121 particleRadius);
122
123 /* Build a lattice and get lattice points for this lattice constant */
124 vector<Vector3d> sites = nanoParticle.getSites();
125 vector<Vector3d> orientations = nanoParticle.getOrientations();
126
127 /* Set up the random number generator engine */
128 std::random_device rd; // Non-deterministic, uniformly-distributed integer
129 // random number generator
130 std::mt19937 gen(rd()); // 32-bit Mersenne Twister random number engine
131
132 std::vector<std::size_t> vacancyTargets;
133 vector<bool> isVacancy;
134
135 Vector3d myLoc;
136 RealType myR;
137
138 for (unsigned int i = 0; i < sites.size(); i++)
139 isVacancy.push_back(false);
140
141 if (args_info.vacancyPercent_given) {
142 if (args_info.vacancyPercent_arg < 0.0 ||
143 args_info.vacancyPercent_arg > 100.0) {
144 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
145 "vacancyPercent was set to a non-sensical value.");
146 painCave.isFatal = 1;
147 simError();
148 } else {
149 RealType vF = args_info.vacancyPercent_arg / 100.0;
150 RealType vIR;
151 RealType vOR;
152 if (args_info.vacancyInnerRadius_given) {
153 vIR = args_info.vacancyInnerRadius_arg;
154 } else {
155 vIR = 0.0;
156 }
157 if (args_info.vacancyOuterRadius_given) {
158 vOR = args_info.vacancyOuterRadius_arg;
159 } else {
160 vOR = particleRadius;
161 }
162 if (vIR >= 0.0 && vOR <= particleRadius && vOR >= vIR) {
163 for (std::size_t i = 0; i < sites.size(); i++) {
164 myLoc = sites[i];
165 myR = myLoc.length();
166 if (myR >= vIR && myR <= vOR) { vacancyTargets.push_back(i); }
167 }
168 std::shuffle(vacancyTargets.begin(), vacancyTargets.end(), gen);
169
170 int nTargets = vacancyTargets.size();
171 vacancyTargets.resize((int)(vF * nTargets));
172
173 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
174 "Removing %d atoms from randomly-selected\n"
175 "\tsites between %lf and %lf.",
176 (int)vacancyTargets.size(), vIR, vOR);
177 painCave.isFatal = 0;
178 painCave.severity = OPENMD_INFO;
179 simError();
180
181 isVacancy.clear();
182 for (std::size_t i = 0; i < sites.size(); i++) {
183 bool vac = false;
184 for (std::size_t j = 0; j < vacancyTargets.size(); j++) {
185 if (i == vacancyTargets[j]) vac = true;
186 }
187 isVacancy.push_back(vac);
188 }
189
190 } else {
191 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
192 "Something is strange about the vacancy\n"
193 "\tinner or outer radii. Check their values.");
194 painCave.isFatal = 1;
195 simError();
196 }
197 }
198 }
199
200 /* Get number of lattice sites */
201 std::size_t nSites = sites.size() - vacancyTargets.size();
202
203 std::vector<Component*> components = simParams->getComponents();
204 std::vector<RealType> molFractions;
205 std::vector<RealType> shellRadii;
206 std::vector<int> nMol;
207 std::map<int, int> componentFromSite;
208 nComponents = components.size();
209
210 if (args_info.molFraction_given && args_info.shellRadius_given) {
211 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
212 "Specify either molFraction or shellRadius "
213 "arguments, but not both!");
214 painCave.isFatal = 1;
215 simError();
216 }
217
218 if (nComponents == 1) {
219 molFractions.push_back(1.0);
220 shellRadii.push_back(particleRadius);
221 } else if (args_info.molFraction_given) {
222 if ((int)args_info.molFraction_given == nComponents) {
223 for (int i = 0; i < nComponents; i++) {
224 molFractions.push_back(args_info.molFraction_arg[i]);
225 }
226 } else if ((int)args_info.molFraction_given == nComponents - 1) {
227 RealType remainingFraction = 1.0;
228 for (int i = 0; i < nComponents - 1; i++) {
229 molFractions.push_back(args_info.molFraction_arg[i]);
230 remainingFraction -= molFractions[i];
231 }
232 molFractions.push_back(remainingFraction);
233 } else {
234 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
235 "nanoparticleBuilder can't figure out molFractions "
236 "for all of the components in the <MetaData> block.");
237 painCave.isFatal = 1;
238 simError();
239 }
240 } else if ((int)args_info.shellRadius_given) {
241 if ((int)args_info.shellRadius_given == nComponents) {
242 for (int i = 0; i < nComponents; i++) {
243 shellRadii.push_back(args_info.shellRadius_arg[i]);
244 }
245 } else if ((int)args_info.shellRadius_given == nComponents - 1) {
246 for (int i = 0; i < nComponents - 1; i++) {
247 shellRadii.push_back(args_info.shellRadius_arg[i]);
248 }
249 shellRadii.push_back(particleRadius);
250 } else {
251 snprintf(
252 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
253 "nanoparticleBuilder can't figure out the\n"
254 "\tshell radii for all of the components in the <MetaData> block.");
255 painCave.isFatal = 1;
256 simError();
257 }
258 } else {
259 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
260 "You have a multi-component <MetaData> block,\n"
261 "\tbut have not specified either molFraction or shellRadius "
262 "arguments.");
263 painCave.isFatal = 1;
264 simError();
265 }
266
267 if (args_info.molFraction_given) {
268 RealType totalFraction = 0.0;
269
270 /* Do some simple sanity checking*/
271
272 for (int i = 0; i < nComponents; i++) {
273 if (molFractions.at(i) < 0.0) {
274 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
275 "One of the requested molFractions was"
276 " less than zero!");
277 painCave.isFatal = 1;
278 simError();
279 }
280 if (molFractions.at(i) > 1.0) {
281 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
282 "One of the requested molFractions was"
283 " greater than one!");
284 painCave.isFatal = 1;
285 simError();
286 }
287 totalFraction += molFractions.at(i);
288 }
289 if (abs(totalFraction - 1.0) > 1e-6) {
290 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
291 "The sum of molFractions was not close enough to 1.0");
292 painCave.isFatal = 1;
293 simError();
294 }
295
296 int remaining = nSites;
297 for (int i = 0; i < nComponents - 1; i++) {
298 nMol.push_back(int((RealType)nSites * molFractions.at(i)));
299 remaining -= nMol.at(i);
300 }
301 nMol.push_back(remaining);
302
303 // recompute actual mol fractions and perform final sanity check:
304
305 std::size_t totalMolecules = 0;
306 for (int i = 0; i < nComponents; i++) {
307 molFractions[i] = (RealType)(nMol.at(i)) / (RealType)nSites;
308 totalMolecules += nMol.at(i);
309 }
310
311 if (totalMolecules != nSites) {
312 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
313 "Computed total number of molecules is not equal "
314 "to the number of lattice sites!");
315 painCave.isFatal = 1;
316 simError();
317 }
318 } else {
319 for (unsigned int i = 0; i < shellRadii.size(); i++) {
320 if (shellRadii.at(i) > particleRadius + 1e-6) {
321 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
322 "One of the shellRadius values exceeds the particle Radius.");
323 painCave.isFatal = 1;
324 simError();
325 }
326 if (shellRadii.at(i) <= 0.0) {
327 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
328 "One of the shellRadius values is smaller than zero!");
329 painCave.isFatal = 1;
330 simError();
331 }
332 }
333 }
334
335 vector<int> ids;
336 if ((int)args_info.molFraction_given) {
337 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
338 "Creating a randomized spherical nanoparticle.");
339 painCave.isFatal = 0;
340 painCave.severity = OPENMD_INFO;
341 simError();
342 /* Random particle is the default case*/
343
344 for (unsigned int i = 0; i < sites.size(); i++)
345 if (!isVacancy[i]) ids.push_back(i);
346
347 std::shuffle(ids.begin(), ids.end(), gen);
348
349 } else {
350 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
351 "Creating a core-shell spherical nanoparticle.");
352 painCave.isFatal = 0;
353 painCave.severity = OPENMD_INFO;
354 simError();
355
356 RealType smallestSoFar;
357 int myComponent = -1;
358 nMol.clear();
359 nMol.resize(nComponents);
360
361 for (unsigned int i = 0; i < sites.size(); i++) {
362 myLoc = sites[i];
363 myR = myLoc.length();
364 smallestSoFar = particleRadius;
365 if (!isVacancy[i]) {
366 for (int j = 0; j < nComponents; j++) {
367 if (myR <= shellRadii[j]) {
368 if (shellRadii[j] <= smallestSoFar) {
369 smallestSoFar = shellRadii[j];
370 myComponent = j;
371 }
372 }
373 }
374 componentFromSite[i] = myComponent;
375 nMol[myComponent]++;
376 }
377 }
378 }
379
380 outputFileName = args_info.output_arg;
381
382 // creat new .omd file on fly which corrects the number of molecule
383 createMdFile(inputFileName, outputFileName, nMol);
384
385 delete oldInfo;
386
387 SimCreator newCreator;
388 SimInfo* NewInfo = newCreator.createSim(outputFileName, false);
389
390 // Place molecules
391 Molecule* mol;
392 SimInfo::MoleculeIterator mi;
393 mol = NewInfo->beginMolecule(mi);
394
395 int l = 0;
396
397 for (int i = 0; i < nComponents; i++) {
398 locator =
399 new MoLocator(NewInfo->getMoleculeStamp(i), NewInfo->getForceField());
400
401 if (!args_info.molFraction_given) {
402 for (unsigned int n = 0; n < sites.size(); n++) {
403 if (!isVacancy[n]) {
404 if (componentFromSite[n] == i) {
405 mol = NewInfo->getMoleculeByGlobalIndex(l);
406 locator->placeMol(sites[n], orientations[n], mol);
407 l++;
408 }
409 }
410 }
411 } else {
412 for (int n = 0; n < nMol.at(i); n++) {
413 mol = NewInfo->getMoleculeByGlobalIndex(l);
414 locator->placeMol(sites[ids[l]], orientations[ids[l]], mol);
415 l++;
416 }
417 }
418 }
419
420 // fill Hmat
421 hmat(0, 0) = 10.0 * particleRadius;
422 hmat(0, 1) = 0.0;
423 hmat(0, 2) = 0.0;
424
425 hmat(1, 0) = 0.0;
426 hmat(1, 1) = 10.0 * particleRadius;
427 hmat(1, 2) = 0.0;
428
429 hmat(2, 0) = 0.0;
430 hmat(2, 1) = 0.0;
431 hmat(2, 2) = 10.0 * particleRadius;
432
433 // set Hmat
434 NewInfo->getSnapshotManager()->getCurrentSnapshot()->setHmat(hmat);
435
436 // create dumpwriter and write out the coordinates
437 writer = new DumpWriter(NewInfo, outputFileName);
438
439 if (writer == NULL) {
440 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
441 "Error in creating dumpwriter object ");
442 painCave.isFatal = 1;
443 simError();
444 }
445
446 writer->writeDump();
447
448 // deleting the writer will put the closing at the end of the dump file
449
450 delete writer;
451
452 // cleanup a by calling sim error.....
453 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
454 "A new OpenMD file called \"%s\" has been "
455 "generated.\n",
456 outputFileName.c_str());
457 painCave.isFatal = 0;
458 painCave.severity = OPENMD_INFO;
459 simError();
460 return 0;
461}
462
463void createMdFile(const std::string& oldMdFileName,
464 const std::string& newMdFileName, std::vector<int> nMol) {
465 ifstream oldMdFile;
466 ofstream newMdFile;
467 const int MAXLEN = 65535;
468 char buffer[MAXLEN];
469
470 // create new .omd file based on old .omd file
471 oldMdFile.open(oldMdFileName.c_str());
472 newMdFile.open(newMdFileName.c_str());
473 oldMdFile.getline(buffer, MAXLEN);
474
475 unsigned int i = 0;
476 while (!oldMdFile.eof()) {
477 // correct molecule number
478 if (strstr(buffer, "nMol") != NULL) {
479 if (i < nMol.size()) {
480 snprintf(buffer, MAXLEN, "\tnMol = %i;", nMol.at(i));
481 newMdFile << buffer << std::endl;
482 i++;
483 }
484 } else
485 newMdFile << buffer << std::endl;
486
487 oldMdFile.getline(buffer, MAXLEN);
488 }
489
490 oldMdFile.close();
491 newMdFile.close();
492
493 if (i != nMol.size()) {
494 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
495 "Couldn't replace the correct number of nMol\n"
496 "\tstatements in component blocks. Make sure that all\n"
497 "\tcomponents in the template file have nMol=1");
498 painCave.isFatal = 1;
499 simError();
500 }
501}
void cmdline_parser_print_help(void)
Print the help.
The only responsibility of SimCreator is to parse the meta-data file and create a SimInfo instance ba...
SimInfo * createSim(const std::string &mdFileName, bool loadInitCoords=true)
Setup Simulation.
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Definition SimInfo.hpp:96
Molecule * getMoleculeByGlobalIndex(int index)
Finds a molecule with a specified global index.
Definition SimInfo.hpp:303
ForceField * getForceField()
Returns the force field.
Definition SimInfo.hpp:269
Molecule * beginMolecule(MoleculeIterator &i)
Returns the first molecule in this SimInfo and intialize the iterator.
Definition SimInfo.cpp:243
MoleculeStamp * getMoleculeStamp(int id)
Returns the molecule stamp.
Definition SimInfo.hpp:293
SnapshotManager * getSnapshotManager()
Returns the snapshot manager.
Definition SimInfo.hpp:251
void setHmat(const Mat3x3d &m)
Sets the H-Matrix.
Definition Snapshot.cpp:220
Snapshot * getCurrentSnapshot()
Returns the pointer of current snapshot.
Real length() const
Returns the length of this vector.
Definition Vector.hpp:397
Implements a spherical lattice.
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
void registerLattice()
Register all lattice.
Definition Register.cpp:134
The header file for the command line option parser generated by GNU Gengetopt version 2....
Where the command line options are stored.
unsigned int vacancyInnerRadius_given
Whether vacancyInnerRadius was given.
double vacancyPercent_arg
Percentage of atoms to remove from within vacancy range.
double vacancyOuterRadius_arg
Radius arround core-shell where vacancies should be located.
unsigned inputs_num
unamed options number
unsigned int molFraction_given
Whether molFraction was given.
double * molFraction_arg
Builds a multi-component random alloy nanoparticle.
char * output_arg
output file name.
char ** inputs
unamed options (options without names)
double * shellRadius_arg
Radius containing within it only molecules of a specific component.
unsigned int shellRadius_given
Whether shellRadius was given.
double radius_arg
Nanoparticle radius in Angstroms.
double vacancyInnerRadius_arg
Radius arround core-shell where vacancies should be located.
unsigned int vacancyPercent_given
Whether vacancyPercent was given.
double latticeConstant_arg
Lattice spacing in Angstroms for cubic lattice.
unsigned int vacancyOuterRadius_given
Whether vacancyOuterRadius was given.