OpenMD 3.0
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
slabBuilder
1#!@Python3_EXECUTABLE@
2
3__author__ = "Dan Gezelter (gezelter@nd.edu), Patrick Louden (plouden@nd.edu)"
4__copyright__ = "Copyright (c) 2004-present The University of Notre Dame. All Rights Reserved."
5__license__ = "OpenMD"
6
7import sys
8import argparse
9import textwrap
10import numpy as np
11from fractions import Fraction
12from math import gcd
13from argparse import RawDescriptionHelpFormatter
14
15def usage():
16 print(__doc__)
17
18def lcm(a, b):
19 """Return lowest common multiple."""
20 return a * b // gcd(a, b)
21
22def fraction_gcd(x, y):
23 a = x.numerator
24 b = x.denominator
25 c = y.numerator
26 d = y.denominator
27 return Fraction(gcd(a, c), lcm(b, d))
28
29
30def find_perpendiculars(hkl):
31 h = hkl[0]
32 k = hkl[1]
33 l = hkl[2]
34 if (h == 0 and k == 0 and l ==0):
35 print("All 3 indices of the facet are zero!")
36 sys.exit(2)
37
38 u1 = np.array([h, k, l])
39
40 # use 111 facet normal as v2 unless we want a slab exposing 111:
41 if (h==k and h==l):
42 v2 = np.array([1, 0, 0])
43 else:
44 v2 = np.array([1, 1, 1])
45
46 # Graham-Schmidt orthogonalization to find u2
47 u2 = v2 - u1 * (float(np.dot(v2, u1))/float(np.dot(u1, u1)))
48
49 # u3 should be perpendicular to both u1 and u2:
50 u3 = np.cross(u1, u2)
51
52 # find smallest integer representation of each of the Miller index
53 # planes:
54 u2Frac = [Fraction(u2[0]).limit_denominator(),
55 Fraction(u2[1]).limit_denominator(),
56 Fraction(u2[2]).limit_denominator()]
57 u2gcd = fraction_gcd(fraction_gcd(u2Frac[0], u2Frac[1]), u2Frac[2])
58 u2min = np.array([int(u2Frac[0]/u2gcd),
59 int(u2Frac[1]/u2gcd),
60 int(u2Frac[2]/u2gcd)])
61
62 u3Frac = [Fraction(u3[0]).limit_denominator(),
63 Fraction(u3[1]).limit_denominator(),
64 Fraction(u3[2]).limit_denominator()]
65 u3gcd = fraction_gcd(fraction_gcd(u3Frac[0], u3Frac[1]), u3Frac[2])
66 u3min = np.array([int(u3Frac[0]/u3gcd),
67 int(u3Frac[1]/u3gcd),
68 int(u3Frac[2]/u3gcd)])
69
70 print("Miller indices of perpendicular slices:", u2min, u3min, u1)
71
72 # Construct rotation matrix between cubic coordinates and new coordinates:
73 xhatn = u2min / np.linalg.norm(u2min)
74 yhatn = u3min / np.linalg.norm(u3min)
75 zhatn = u1 / np.linalg.norm(u1)
76 rotMat = np.vstack((xhatn, yhatn, zhatn))
77
78 # Construct the vector of minimum repeat distances for these slices:
79 xmin = np.linalg.norm(u2min)
80 ymin = np.linalg.norm(u3min)
81 zmin = np.linalg.norm(u1)
82 minVec = np.array([xmin, ymin, zmin])
83
84 return (rotMat, minVec)
85
86def make_lattice(lattice, latticeConstant, rotMat, minVec, repeats, vacuum, chargedPlates, offSet):
87 basis = {
88 "sc": [np.array([0.0, 0.0, 0.0])],
89 "fcc": [np.array([0.0, 0.0, 0.0]), np.array([0.5, 0.5, 0.0]),
90 np.array([0.5, 0.0, 0.5]), np.array([0.0, 0.5, 0.5])],
91 "bcc": [np.array([0.0, 0.0, 0.0]), np.array([0.5, 0.5, 0.5])]
92 }
93 theBasis = basis.get(lattice)
94 coords=[]
95
96 rInv = np.transpose(rotMat)
97 boxCorner = np.array([0.0, 0.0, 0.0])
98 rMin = np.array([0.0, 0.0, 0.0])
99 rMax = np.array([0.0, 0.0, 0.0])
100 for i in range(2):
101 for j in range(2):
102 for k in range(2):
103 boxCorner[0] = i * minVec[0]*repeats[0]*latticeConstant
104 boxCorner[1] = j * minVec[1]*repeats[1]*latticeConstant
105 boxCorner[2] = k * minVec[2]*repeats[2]*latticeConstant
106 box = rInv.dot(boxCorner) / latticeConstant
107 for l in range(3):
108 if (box[l] < rMin[l]):
109 rMin[l] = box[l]
110 if (box[l]>rMax[l]):
111 rMax[l] = box[l]
112
113 eps = 1.0-3
114
115 for k in range(int(rMin[2])-2, int(rMax[2])+2):
116 for j in range(int(rMin[1])-2, int(rMax[1])+2):
117 for i in range(int(rMin[0])-2, int(rMax[0])+2):
118 cellOrigin = np.array([float(i), float(j), float(k)])
119 for l in range(len(theBasis)):
120 pos = latticeConstant * (cellOrigin + theBasis[l])
121 pos = rotMat.dot(pos)
122 # slicing is in new coordinates with x,y, and z axes:
123 if (pos[0] > -eps and pos[0] < boxCorner[0]-eps):
124 if (pos[1] > -eps and pos[1] < boxCorner[1]-eps):
125 if (pos[2] > -eps and pos[2] < boxCorner[2]-eps):
126 coords.append(pos)
127
128 print("Generated slab has " + str(len(coords)) + " atoms.")
129
130 Hmat = [[boxCorner[0], 0, 0], [0, boxCorner[1], 0], [0, 0, boxCorner[2]]]
131
132 print("Generated slab has dimensions: ", boxCorner)
133
134 # Wrap the coordinates so that the center of mass is located at
135 # the origin:
136 com = [0.0, 0.0, 0.0]
137 for i in range(len(coords)):
138 for j in range(0, 3):
139 com[j] = com[j] + coords[i][j]
140 for i in range(0, 3):
141 com[i] = com[i] / float(len(coords))
142 for i in range(len(coords)):
143 for j in range(0, 3):
144 coords[i][j] = coords[i][j] - com[j]
145
146 if (vacuum.lower() == "true"):
147 # Multiplying the z-dimenion by 3 so the resulting box
148 # contains 2/3 vacuum and 1/3 solid
149 if (chargedPlates.lower() == 'true'):
150 Hmat = [[boxCorner[0], 0, 0], [0, boxCorner[1], 0], [0, 0, (boxCorner[2]*3.0) + 2*offSet]]
151 elif (chargedPlates.lower() == 'false'):
152 Hmat = [[boxCorner[0], 0, 0], [0, boxCorner[1], 0], [0, 0, boxCorner[2]*3.0]]
153 else:
154 Hmat = [[boxCorner[0], 0, 0], [0, boxCorner[1], 0], [0, 0, boxCorner[2]]]
155
156
157 return coords, Hmat
158
159
160def make_plates(coords, Hmat, kPoints, offSet):
161
162 plates = []
163
164 maxZ = 0.0
165 minZ = 0.0
166 for i in range (0, len(coords)):
167 if (coords[i][2] > maxZ):
168 maxZ = coords[i][2]
169 if (coords[i][2] < minZ):
170 minZ = coords[i][2]
171
172 print("minZ = ", minZ)
173 print("maxZ = ", maxZ)
174
175 dx = Hmat[0][0]/kPoints
176 dy = Hmat[1][1]/kPoints
177
178 for i in range(0, int(kPoints)):
179 for j in range(0, int(kPoints)):
180 platePoint = [i*dx - (Hmat[0][0]/2.0), j*dy - (Hmat[1][1]/2.0), minZ - offSet]
181 plates.append(platePoint)
182
183 for i in range(0, int(kPoints)):
184 for j in range(0, int(kPoints)):
185 platePoint = [i*dx- (Hmat[0][0]/2.0), j*dy - (Hmat[1][1]/2.0), maxZ + offSet]
186 plates.append(platePoint)
187
188
189 return plates
190
191
192def write_xyz_file(outputFileName, coords, elementType, Hmat, plates):
193 outputFile = open(outputFileName, 'w')
194 outputFile.write("%d\n" % (len(coords) + len(plates)))
195 outputFile.write("\t%f%s\t%f\t%f\t%f%s\t%f\t%f\t%f%s\t%f\t%f\t%f\n" %
196 (0.00000, ";",
197 Hmat[0][0], Hmat[0][1], Hmat[0][2], ";",
198 Hmat[1][0], Hmat[1][1], Hmat[1][2], ";",
199 Hmat[2][0], Hmat[2][1], Hmat[2][2]))
200 for i in range(len(coords)):
201 outputFile.write(elementType + " \t%f\t%f\t%f\n" %
202 (coords[i][0], coords[i][1], coords[i][2]))
203 for i in range(len(plates)):
204 outputFile.write("Pt" + " \t%f\t%f\t%f\n" %
205 (plates[i][0], plates[i][1], plates[i][2]))
206
207 outputFile.close()
208 print("Generated XYZ file: " + outputFileName)
209
210def write_omd_file(outputFileName, coords, elementType, Hmat, plates, directional):
211 outputFile = open(outputFileName, 'w')
212 outputFile.write("<OpenMD version=2>\n")
213 outputFile.write(" <MetaData> \n\n")
214 if (len(plates) > 0):
215 outputFile.write("#include \"header.inc\" \n")
216 outputFile.write("molecule{ \n")
217 outputFile.write(" name = \"" + elementType + "\";\n\n")
218 outputFile.write(" atom[0]{\n")
219 outputFile.write(" type=\"" + elementType + "\";\n")
220 outputFile.write(" position( 0.0, 0.0, 0.0 );\n")
221 outputFile.write(" }\n")
222 outputFile.write("}\n\n")
223 outputFile.write("component{ \n")
224 outputFile.write(" type = \"" + elementType + "\";\n")
225 outputFile.write(" nMol = " + str(len(coords)) + ";\n")
226 outputFile.write("}\n\n")
227 if (len(plates) > 0):
228 outputFile.write("component{ \n")
229 outputFile.write(" type = \"Plates\";\n")
230 outputFile.write(" nMol = " + str(1) + ";\n")
231 outputFile.write("}\n\n")
232 outputFile.write("ensemble = NgammaT;\n")
233 outputFile.write("surfaceTension = 0.0;\n")
234 outputFile.write("forceField = \"MnM\";\n")
235 outputFile.write("cutoffRadius = 9.0; \n")
236 outputFile.write("cutoffMethod = \"shifted_force\";\n\n")
237 outputFile.write("targetTemp = 100.0;\n")
238 outputFile.write("tauThermostat = 4e3;\n")
239 outputFile.write("targetPressure = 1.0;\n")
240 outputFile.write("tauBarostat = 1e4;\n")
241 outputFile.write("tempSet = \"false\";\n")
242 outputFile.write("thermalTime = 1e3; \n\n")
243 outputFile.write("runTime = 1e4; \n")
244 outputFile.write("dt = 2.0; \n\n")
245 outputFile.write("sampleTime = 1000;\n")
246 outputFile.write("statusTime = 100;\n\n")
247 outputFile.write("printPressureTensor = \"true\";\n\n")
248 outputFile.write(" </MetaData>\n")
249 outputFile.write(" <Snapshot>\n")
250 outputFile.write(" <FrameData>\n")
251 outputFile.write(" Time: 0 \n")
252 outputFile.write(" Hmat: {{ %f, %f, %f }, { %f, %f, %f }, { %f, %f, %f }}\n" %
253 (Hmat[0][0], Hmat[0][1], Hmat[0][2], Hmat[1][0], Hmat[1][1],
254 Hmat[1][2], Hmat[2][0], Hmat[2][1], Hmat[2][2]))
255 outputFile.write(" Thermostat: 0.0, 0.0 \n")
256 outputFile.write(" Barostat: {{ 0, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 }} \n")
257 outputFile.write(" </FrameData>\n")
258 outputFile.write(" <StuntDoubles> \n")
259 if (directional):
260 for i in range(len(coords)):
261 outputFile.write("\t" + str(i) + "\t%s\t%f\t%f\t%f\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" %
262 ("pvqj", coords[i][0], coords[i][1], coords[i][2],
263 "0.0", "0.0", "0.0","1.0","0.0","0.0","0.0","0.0","0.0","0.0"))
264 else:
265 for i in range(len(coords)):
266 outputFile.write("\t" + str(i) + "\t%s\t%f\t%f\t%f\t%s\t%s\t%s\n" %
267 ("pv", coords[i][0], coords[i][1], coords[i][2],
268 "0.0", "0.0", "0.0"))
269
270 #for i in range(len(plates)):
271 #outputFile.write("\t" + str(i+len(coords)) + "\t%s\t%f\t%f\t%f\t%s\t%s\t%s\n" %
272 # ("pv", plates[i][0], plates[i][1], plates[i][2],
273 # "0.0", "0.0", "0.0"))
274 if (len(plates) > 0):
275 outputFile.write("\t" + str(len(coords)) + "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" % ("pvqj", "0.0", "0.0", "0.0", "0.0", "0.0", "0.0", "1.0", "0.0", "0.0", "0.0", "0.0", "0.0", "0.0" ))
276 outputFile.write(" </StuntDoubles> \n")
277 outputFile.write(" </Snapshot>\n")
278 outputFile.write("</OpenMD>")
279 outputFile.close()
280 print("Generated OpenMD file: " + outputFileName)
281
282
283def writeHeaderFile(plates):
284
285 headerFile = open("header.inc", 'w')
286
287 headerFile.write("#ifndef _HEADER_INC_ \n")
288 headerFile.write("#define _HEADER_INC_ \n\n")
289 headerFile.write("molecule{ \n")
290 headerFile.write(" name = \"Plates\"; \n\n")
291 for i in range(0, int(len(plates)/2)):
292 headerFile.write(" atom[" + str(i) +"]{ \n")
293 headerFile.write(" type = \"PosPlate\"; \n")
294 headerFile.write(" position( " + str(plates[i][0]) + ", " + str(plates[i][1]) + ", " +str(plates[i][2]) + " ); \n")
295 headerFile.write(" } \n")
296 for i in range(int(len(plates)/2), len(plates)):
297 headerFile.write(" atom[" + str(i) +"]{ \n")
298 headerFile.write(" type = \"NegPlate\"; \n")
299 headerFile.write(" position( " + str(plates[i][0]) + ", " + str(plates[i][1]) + ", " +str(plates[i][2]) + " ); \n")
300 headerFile.write(" } \n")
301 headerFile.write(" rigidBody[0]{ \n")
302 headerFile.write(" members(")
303 for i in range(0, len(plates)):
304 if (i < len(plates) - 1):
305 headerFile.write( str(i) + ", ")
306 elif (i == len(plates) -1):
307 headerFile.write( str(i) )
308 headerFile.write("); \n")
309 headerFile.write(" } \n } \n")
310 headerFile.write("#endif \n")
311
312
313
314
315
316
317def main(argv):
318 parser = argparse.ArgumentParser(
319 description='OpenMD cubic lattice slab builder\nBuilds a slab of a cubic material (SC, FCC, or BCC) with a particular cut (hkl) facing the z-axis of the box.',
320 formatter_class=RawDescriptionHelpFormatter,
321 epilog="Example: slabBuilder -l fcc -c 4.08 -f 5 5 7 -r 10 10 8 -e Au -v true -o 557Slab.omd -x 557Slab.xyz")
322 parser.add_argument("-l", "--lattice=", action="store",
323 choices=('sc', 'fcc', 'bcc'), dest="lattice", help="One of: sc, fcc, or bcc")
324 parser.add_argument("-c", "--latticeConstant=", action="store", type=float,
325 dest="latticeConstant", help="Lattice spacing in Angstroms")
326 parser.add_argument("-o", "--omd-file=", action="store", dest="omdFileName", help="use specified output (.omd) file")
327 parser.add_argument("-x", "--xyz-file=", action="store", dest="xyzFileName", help="use specified output (.xyz) file")
328 parser.add_argument("-f", "--hkl", action="store", type=int, nargs=3,
329 dest="hkl", help="expose this facet to the z axis (specify with three separate integers)")
330 parser.add_argument("-r", "--repeats", action="store", type=int, nargs=3,
331 dest="repeats", default=[1, 1, 1], help="how many lattice repeats in each of the 3 perpendicular directions (specify with three separate integers)")
332 parser.add_argument("-e", "--elementType=", action="store",
333 dest="elementType", help=" the element composing the lattice")
334 parser.add_argument("-v", "--vacuum=", action="store",
335 choices=('true', 'false'), dest="vacuum", default='true', help="true/false (Should the output file have vacuum in the z-dimension?)")
336 parser.add_argument("-d", action="store_true",
337 dest="directional", default = False, help="Flag for directional atom")
338 parser.add_argument("-q", "--chargedPlates", action="store",
339 dest="chargedPlates", choices=('true', 'false'), default='false', help="true/false (Should the output file include charged plates?)")
340 parser.add_argument("-k", "--kPoints", action="store", dest="kPoints", type=float, default=20, help="number of points to use in the charged plates")
341 parser.add_argument("-s", "--offSet", action="store", dest="offSet", type=float, default=10, help="the distance the charged plates are from the surface")
342
343 if len(sys.argv) == 1:
344 parser.print_help()
345 sys.exit(2)
346 args = parser.parse_args()
347
348 if (not args.lattice):
349 parser.error("No lattice was specified")
350
351 if (not args.latticeConstant):
352 parser.print_help()
353 parser.error("No lattice constant was specified")
354
355 if ((not args.omdFileName) and (not args.xyzFileName)):
356 parser.print_help()
357 parser.error("No output file was specified")
358
359 if (not args.hkl):
360 parser.print_help()
361 parser.error("No facet (hkl) was specified")
362
363 if (not args.repeats):
364 parser.print_help()
365 parser.error("No repeat was specified")
366
367 if (not args.elementType):
368 parser.print_help()
369 parser.error("No elementType specified")
370
371
372 (rotMat, minVec) = find_perpendiculars(args.hkl)
373 coords, Hmat = make_lattice(args.lattice, args.latticeConstant, rotMat,
374 minVec, args.repeats, args.vacuum,
375 args.chargedPlates, args.offSet)
376 if (args.chargedPlates.lower() == 'true'):
377 plates = make_plates(coords, Hmat, args.kPoints, args.offSet)
378 writeHeaderFile(plates)
379 elif(args.chargedPlates.lower() == 'false'):
380 plates = []
381
382 if (args.xyzFileName):
383 write_xyz_file(args.xyzFileName, coords, args.elementType, Hmat, plates)
384 if (args.omdFileName):
385 write_omd_file(args.omdFileName, coords, args.elementType, Hmat, plates, args.directional)
386
387
388
389if __name__ == "__main__":
390 #if len(sys.argv) == 1:
391 #parser.print_help()
392 #usage() # need to change to call argeparse stuffs
393 #sys.exit()
394 main(sys.argv[1:])