2"""principalAxisCalculator
4Opens an XYZ file and computes the moments of inertia and principal
5axes for the structure in the XYZ file. Optionally rotates the
6structure so that the long axis (that with the smallest eigenvalue) is
7pointing along the z-axis.
9Usage: principalAxisCalculator
12 -h, --help show this help
13 -x, --xyz=... use specified XYZ (.xyz) file for the structure
14 -o, --out=... rotate the structure so that the smallest eigenvalue
15 of the rotation matrix points along the z-axis.
18 principalAxisCalculator -x junk.xyz -o rot.xyz
22__author__ = "Dan Gezelter (gezelter@nd.edu)"
23__copyright__ = "Copyright (c) 2004-present The University of Notre Dame. All Rights Reserved."
32from functools import reduce
77 'O_TIP4P-Ew': 15.9994,
108 return reduce(add, seq)
110def readFile(XYZFileName):
111 print("reading XYZ file")
113 XYZFile = open(XYZFileName, 'r')
114 # Find number of atoms first
115 line = XYZFile.readline()
119 line = XYZFile.readline()
120 for i in range(nAtoms):
121 line = XYZFile.readline()
124 indices.append(myIndex)
126 atypes.append(atomType)
130 positions.append([x, y, z])
141 for i in range(0, len(indices)):
142 myMass = mass_table[atypes[i]]
144 Xcom = Xcom + myMass * positions[i][0]
145 Ycom = Ycom + myMass * positions[i][1]
146 Zcom = Zcom + myMass * positions[i][2]
147 totalMass = totalMass + myMass
149 Xcom = Xcom / totalMass
150 Ycom = Ycom / totalMass
151 Zcom = Zcom / totalMass
153 COM = [Xcom, Ycom, Zcom]
161 #find inertia tensor matrix elements
163 I = numpy.zeros((3, 3), dtype=float)
165 for i in range(0, len(indices)):
166 myMass = mass_table[atypes[i]]
168 dx = positions[i][0] - COM[0]
169 dy = positions[i][1] - COM[1]
170 dz = positions[i][2] - COM[2]
172 I[0, 0] = I[0, 0] + myMass * ( dy * dy + dz * dz )
173 I[1, 1] = I[1, 1] + myMass * ( dx * dx + dz * dz )
174 I[2, 2] = I[2, 2] + myMass * ( dx * dx + dy * dy )
176 I[0, 1] = I[0, 1] - myMass * ( dx * dy )
177 I[0, 2] = I[0, 2] - myMass * ( dx * dz )
179 I[1, 2] = I[1, 2] - myMass * ( dy * dz )
185 print("Inertia Tensor:")
189 (evals, evects) = numpy.linalg.eig(I)
196 print("Center of mass:")
200 return (COM, evals, evects)
202def writeFile(OutFileName):
204 (COM, evals, evects) = findMoments()
206 # we need to re-order the axes so that the smallest moment of inertia
207 # (which corresponds to the long axis of the molecule) is along the z-axis
208 # we'll just reverse the order of the three axes:
210 axOrder = numpy.argsort(evals)
211 RotMat = numpy.zeros((3, 3), dtype=float)
213 achk1 = (axOrder == numpy.array([0, 1, 2])).all()
214 achk2 = (axOrder == numpy.array([1, 2, 0])).all()
215 achk3 = (axOrder == numpy.array([2, 0, 1])).all()
216 if achk1 == True or achk2 == True or achk3 == True:
217 RotMat[0] = evects[axOrder[2]]
218 RotMat[1] = evects[axOrder[1]]
219 RotMat[2] = evects[axOrder[0]]
221 RotMat[0] = evects[axOrder[1]]
222 RotMat[1] = evects[axOrder[2]]
223 RotMat[2] = evects[axOrder[0]]
225 #RotMat[0] = evects[axOrder[2]]
226 #RotMat[1] = evects[axOrder[1]]
227 #RotMat[2] = evects[axOrder[0]]
229 q = [0.0, 0.0, 0.0, 0.0]
230 myEuler = [0.0, 0.0, 0.0]
232 # RotMat to Quat code is out of OpenMD's SquareMatrix3.hpp code:
234 t = RotMat[0][0] + RotMat[1][1] + RotMat[2][2] + 1.0
237 s = 0.5 / math.sqrt( t )
239 q[1] = (RotMat[1][2] - RotMat[2][1]) * s
240 q[2] = (RotMat[2][0] - RotMat[0][2]) * s
241 q[3] = (RotMat[0][1] - RotMat[1][0]) * s
247 if( ad1 >= ad2 and ad1 >= ad3 ):
248 s = 0.5 / math.sqrt( 1.0 + RotMat[0][0] - RotMat[1][1] - RotMat[2][2] )
249 q[0] = (RotMat[1][2] - RotMat[2][1]) * s
251 q[2] = (RotMat[0][1] + RotMat[1][0]) * s
252 q[3] = (RotMat[0][2] + RotMat[2][0]) * s
253 elif ( ad2 >= ad1 and ad2 >= ad3 ):
254 s = 0.5 / math.sqrt( 1.0 + RotMat[1][1] - RotMat[0][0] - RotMat[2][2] )
255 q[0] = (RotMat[2][0] - RotMat[0][2] ) * s
256 q[1] = (RotMat[0][1] + RotMat[1][0]) * s
258 q[3] = (RotMat[1][2] + RotMat[2][1]) * s
260 s = 0.5 / math.sqrt( 1.0 + RotMat[2][2] - RotMat[0][0] - RotMat[1][1] )
261 q[0] = (RotMat[0][1] - RotMat[1][0]) * s
262 q[1] = (RotMat[0][2] + RotMat[2][0]) * s
263 q[2] = (RotMat[1][2] + RotMat[2][1]) * s
265 print("Quaternions:")
269 theta = math.acos(RotMat[2][2])
270 ctheta = RotMat[2][2]
271 stheta = math.sqrt(1.0 - ctheta * ctheta)
273 if (math.fabs(stheta) < 1e-6):
275 phi = math.atan2(-RotMat[1][0], RotMat[0][0])
277 phi = math.atan2(RotMat[2][0], -RotMat[2][1])
278 psi = math.atan2(RotMat[0][2], RotMat[1][2])
281 phi = phi + 2.0 * math.pi;
284 psi = psi + 2.0 * math.pi;
286 myEuler[0] = phi * 180.0 / math.pi;
287 myEuler[1] = theta * 180.0 / math.pi;
288 myEuler[2] = psi * 180.0 / math.pi;
290 print("Euler Angles:")
293 nAtoms = len(indices)
295 print("writing output XYZ file")
297 OutFile = open(OutFileName, 'w')
299 OutFile.write('%10d\n' % (nAtoms))
302 for i in range(nAtoms):
304 dx = positions[i][0] - COM[0]
305 dy = positions[i][1] - COM[1]
306 dz = positions[i][2] - COM[2]
308 r = numpy.array([dx, dy, dz])
309 rnew = numpy.dot(RotMat, r)
311 OutFile.write('%s\t%f\t%f\t%f\n' % (atypes[i], rnew[0], rnew[1], rnew[2]))
316 opts, args = getopt.getopt(argv, "hx:o:", ["help", "xyz=", "out="])
317 except getopt.GetoptError:
320 for opt, arg in opts:
321 if opt in ("-h", "--help"):
324 elif opt in ("-x", "--xyz"):
326 global _haveXYZFileName
328 elif opt in ("-o", "--out"):
330 global _haveOutFileName
334 if (_haveXYZFileName != 1):
336 print("No xyz file was specified")
339 readFile(XYZFileName)
341 if (_haveOutFileName == 1):
342 writeFile(OutFileName)
346if __name__ == "__main__":
347 if len(sys.argv) == 1: