ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
readfrq.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 from __future__ import division
4 from __future__ import print_function
5 from __future__ import absolute_import
6 from builtins import zip
7 from builtins import range
8 import os, sys, re
9 import numpy as np
10 from .molecule import Molecule, Elements
11 from .nifty import isint, isfloat
12 
13 np.set_printoptions(precision=4)
14 
15 def print_mode(M, mode):
16  print('\n'.join(['%-3s' % M.elem[ii] + ' '.join(['% 7.3f' % j for j in i]) for ii, i in enumerate(mode)]))
17 
18 def read_frq_gau(gauout):
19  XMode = 0
20  xyz = []
21  elem = []
22  elemThis = []
23  VMode = 0
24  frqs = []
25  intens = []
26  modes = []
27  for line in open(gauout).readlines():
28  line = line.strip().expandtabs()
29  if XMode >= 1:
30  # Perfectionist here; matches integer, element, and three floating points
31  if re.match("^[0-9]+ +[0-9]+ +[0-9]+( +[-+]?([0-9]*\.)?[0-9]+){3}$", line):
32  XMode = 2
33  sline = line.split()
34  elemThis.append(Elements[int(sline[1])])
35  xyz.append([float(i) for i in sline[3:]])
36  elif XMode == 2: # Break out of the loop if we encounter anything other than atomic data
37  if elem == []:
38  elem = elemThis
39  elif elem != elemThis:
40  logger.error('Gaussian output parser will not work if successive calculations have different numbers of atoms!\n')
41  raise RuntimeError
42  elemThis = []
43  xyz = np.array(xyz)
44  XMode = -1
45  elif XMode == 0 and "Coordinates (Angstroms)" in line:
46  XMode = 1
47  VModeNxt = None
48  if line.strip().startswith('Frequencies'):
49  VMode = 2
50  if VMode == 2:
51  s = line.split()
52  if 'Frequencies' in line:
53  nfrq = len(s) - 2
54  frqs += [float(i) for i in s[2:]]
55  if re.match('^[ \t]*Atom', line):
56  VModeNxt = 3
57  readmodes = [[] for i in range(nfrq)]
58  if 'IR Inten' in line:
59  intens += [float(i) for i in s[3:]]
60  if 'Imaginary Frequencies' in line:
61  VMode = 0
62  if VMode == 3:
63  s = line.split()
64  if len(s) != nfrq*3+2:
65  VMode = 0
66  modes += readmodes[:]
67  else:
68  for i in range(nfrq):
69  readmodes[i].append([float(s[j]) for j in range(2+3*i,5+3*i)])
70  if VModeNxt is not None: VMode = VModeNxt
71  unnorm = [np.array(i) for i in modes]
72  return np.array(frqs), [i/np.linalg.norm(i) for i in unnorm], np.array(intens), elem, xyz
73 
74 def read_frq_tc(tcout, scrdir='scr'):
75  # Unfortunately, TeraChem's frequency data is scattered in the output file and scratch folder
76  lineCounter = -100
77  xyzpath = os.path.join(os.path.split(os.path.abspath(tcout))[0], scrdir, 'CentralGeometry.initcond.xyz')
78  tcdat = os.path.join(os.path.split(os.path.abspath(tcout))[0], scrdir, 'Frequencies.dat')
79  if not os.path.exists(xyzpath):
80  raise RuntimeError("%s doesn't exist; please provide a scratch folder to this function" % xyzpath)
81  if not os.path.exists(tcdat):
82  raise RuntimeError("%s doesn't exist; please provide a scratch folder to this function" % tcdat)
83  Mxyz = Molecule(xyzpath)
84 
85  # This piece of Yudong's code reads the intensities
86  found_vib = False
87  freqs = []
88  intensities = []
89  for line in open(tcout):
90  if 'Vibrational Frequencies/Thermochemical Analysis After Removing Rotation and Translation' in line:
91  found_vib = True
92  if found_vib:
93  ls = line.split()
94  if len(ls) == 8 and ls[0].isdigit():
95  freqs.append(float(ls[2]))
96  intensities.append(float(ls[3]))
97  elif len(ls) == 3 and ls[2].endswith('i'):
98  freqs.append(-1*float(ls[2][:-1]))
99  intensities.append(0.0)
100  if line.strip() == '':
101  break
102  if found_vib is False:
103  raise RuntimeError("No frequency data was found in file %s" % filename)
104 
105  for lineNumber, line in enumerate(open(tcdat).readlines()):
106  s = line.split()
107  if lineNumber == 0:
108  numAtoms = int(s[-1])
109  elif lineNumber == 1:
110  numModes = int(s[-1])
111  # Make list of unnormalized modes to be read in
112  frqs = np.zeros(numModes, dtype=float)
113  unnorm = [np.zeros(3*numAtoms, dtype=float) for i in range(numModes)]
114  elif all([isint(i) for i in s]):
115  lineCounter = 0
116  modeNumbers = [int(i) for i in s]
117  elif lineCounter == 1:
118  theseFrqs = [float(i) for i in s]
119  if len(theseFrqs) != len(modeNumbers):
120  raise RuntimeError('Parser error! Expected # frequencies to equal # modes')
121  for i in range(len(theseFrqs)):
122  frqs[modeNumbers[i]] = theseFrqs[i]
123  elif lineCounter >= 3:
124  if lineCounter%3 == 0:
125  if not isint(s[0]):
126  raise RuntimeError('Parser error! Expected integer at start of line')
127  disps = [float(i) for i in s[1:]]
128  else:
129  disps = [float(i) for i in s]
130  idx = lineCounter-3
131  if len(disps) != len(modeNumbers):
132  raise RuntimeError('Parser error! Expected # displacements to equal # modes')
133  for i in range(len(disps)):
134  unnorm[modeNumbers[i]][lineCounter-3] = disps[i]
135  if idx == 3*numAtoms-1:
136  lineCounter = -100
137  lineCounter += 1
138  if np.max(np.abs(np.array(frqs)-np.array(freqs))) > 1.0:
139  raise RuntimeError("Inconsistent frequencies from TeraChem output and scratch")
140  return np.array(frqs), [i/np.linalg.norm(i) for i in unnorm], np.array(intensities), Mxyz.elem, Mxyz.xyzs[0]
141 
142 def read_frq_qc(qcout):
143  XMode = 0
144  xyz = []
145  elem = []
146  elemThis = []
147  VMode = 0
148  frqs = []
149  modes = []
150  intens = []
151  for line in open(qcout).readlines():
152  line = line.strip().expandtabs()
153  if XMode >= 1:
154  # Perfectionist here; matches integer, element, and three floating points
155  if re.match("^[0-9]+ +[A-Z][A-Za-z]?( +[-+]?([0-9]*\.)?[0-9]+){3}$", line):
156  XMode = 2
157  sline = line.split()
158  elemThis.append(sline[1])
159  xyz.append([float(i) for i in sline[2:]])
160  elif XMode == 2: # Break out of the loop if we encounter anything other than atomic data
161  if elem == []:
162  elem = elemThis
163  elif elem != elemThis:
164  logger.error('Q-Chem output parser will not work if successive calculations have different numbers of atoms!\n')
165  raise RuntimeError
166  elemThis = []
167  xyz = np.array(xyz)
168  XMode = -1
169  elif XMode == 0 and re.match("Standard Nuclear Orientation".lower(), line.lower()):
170  XMode = 1
171  VModeNxt = None
172  if 'VIBRATIONAL ANALYSIS' in line:
173  VMode = 1
174  if VMode > 0 and line.strip().startswith('Mode:'):
175  VMode = 2
176  if VMode == 2:
177  s = line.split()
178  if 'Frequency:' in line:
179  nfrq = len(s) - 1
180  frqs += [float(i) for i in s[1:]]
181  if re.match('^X +Y +Z', line):
182  VModeNxt = 3
183  readmodes = [[] for i in range(nfrq)]
184  if 'IR Intens:' in line:
185  intens += [float(i) for i in s[2:]]
186  if 'Imaginary Frequencies' in line:
187  VMode = 0
188  if VMode == 3:
189  s = line.split()
190  if len(s) != nfrq*3+1:
191  VMode = 2
192  modes += readmodes[:]
193  elif 'TransDip' not in s:
194  for i in range(nfrq):
195  readmodes[i].append([float(s[j]) for j in range(1+3*i,4+3*i)])
196  if VModeNxt is not None: VMode = VModeNxt
197  unnorm = [np.array(i) for i in modes]
198  return np.array(frqs), [i/np.linalg.norm(i) for i in unnorm], np.array(intens), elem, xyz
199 
200 def read_frq_psi(psiout):
201  """ """
202  VMode = 0
203  XMode = 0
204  EMode = 0
205  frqs = []
206  modes = []
207  xyzs = []
208  xyz = []
209  elem = []
210  for line in open(psiout).readlines():
211  VModeNxt = None
212  if 'Frequency:' in line:
213  VModeNxt = 1
214  if line.split()[-1].endswith('i'):
215  frqs.append(-1*float(line.split()[-1][:-1]))
216  # frqs.append(0.0) # After the optimization this mode is going to be useless...
217  else:
218  frqs.append(float(line.split()[-1]))
219  if VMode == 1:
220  if re.match('^[ \t]+X', line):
221  VModeNxt = 2
222  readmode = []
223  if VMode == 2:
224  s = line.split()
225  if len(s) != 5:
226  VMode = 0
227  modes.append(readmode[:])
228  else:
229  m = float(s[-1])
230  # Un-massweight the eigenvectors so that the output matches Q-Chem or Gaussian.
231  readmode.append([float(i)/np.sqrt(m) for i in s[1:4]])
232  if VModeNxt is not None: VMode = VModeNxt
233  if XMode == 1:
234  s = line.split()
235  if len(s) == 4 and isfloat(s[1]) and isfloat(s[2]) and isfloat(s[3]):
236  e = s[0]
237  xyz.append([float(i) for i in s[1:4]])
238  if EMode == 1:
239  elem.append(e)
240  elif len(xyz) > 0:
241  xyzs.append(np.array(xyz))
242  xyz = []
243  XMode = 0
244  if line.strip().startswith("Geometry (in Angstrom)"):
245  XMode = 1
246  EMode = len(elem) == 0
247  unnorm = [np.array(i) for i in modes]
248  return np.array(frqs), [i/np.linalg.norm(i) for i in unnorm], np.zeros_like(frqs), elem, np.array(xyzs[-1])
249 
250 def read_frq_fb(vfnm):
251  """ Read ForceBalance-formatted vibrational data from a vdata.txt file. """
252 
253  na = -1
254  ref_eigvals = []
255  ref_eigvecs = []
256  an = 0
257  ln = 0
258  cn = -1
259  elem = []
260  for line in open(vfnm):
261  line = line.split('#')[0] # Strip off comments
262  s = line.split()
263  if len(s) == 1 and na == -1:
264  na = int(s[0])
265  xyz = np.zeros((na, 3))
266  cn = ln + 1
267  elif ln == cn:
268  pass
269  elif an < na and len(s) == 4:
270  elem.append(s[0])
271  xyz[an, :] = np.array([float(i) for i in s[1:]])
272  an += 1
273  elif len(s) == 1:
274  ref_eigvals.append(float(s[0]))
275  ref_eigvecs.append(np.zeros((na, 3)))
276  an = 0
277  elif len(s) == 3:
278  ref_eigvecs[-1][an, :] = np.array([float(i) for i in s])
279  an += 1
280  elif len(s) == 0:
281  pass
282  else:
283  logger.info(line + '\n')
284  logger.error("This line doesn't comply with our vibration file format!\n")
285  raise RuntimeError
286  ln += 1
287  ref_eigvals = np.array(ref_eigvals)
288  ref_eigvecs = np.array(ref_eigvecs)
289  for v2 in ref_eigvecs:
290  v2 /= np.linalg.norm(v2)
291  return ref_eigvals, ref_eigvecs, np.zeros_like(ref_eigvals), elem, xyz
292 
293 def scale_freqs(arr):
294  """ Apply harmonic vibrational scaling factors. """
295  # Scaling factors are taken from:
296  # Jeffrey P. Merrick, Damian Moran, and Leo Radom
297  # An Evaluation of Harmonic Vibrational Frequency Scale Factors
298  # J. Phys. Chem. A 2007, 111, 11683-11700
299  #----
300  # The dividing line is just a guess (used by A. K. Wilson)
301  div = 1000.0
302  # High-frequency scaling factor for MP2/aTZ
303  hscal = 0.960
304  # Low-frequency scaling factor for MP2/aTZ
305  lscal = 1.012
306  print(" Freq(Old) Freq(New) RawShift NewShift DShift")
307  def scale_one(frq):
308  if frq > div:
309  if hscal < 1.0:
310  # Amount that the frequency is above the dividing line
311  # above = (frq-div)
312  # Maximum frequency shift
313  # maxshf = (div/hscal-div)
314  # Close to the dividing line, the frequency should be
315  # scaled less because we don't want the orderings of
316  # the frequencies to switch.
317  # Far from the dividing line, we want the frequency shift
318  # to approach the uncorrected shift.
319  # 1.0/(1.0 + maxshf/above) is a scale of how far we are from the dividing line.
320  # att = 1.0/(1.0 + maxshf/above)
321  # shift is the uncorrected shift.
322  att = (frq-div)/(frq-hscal*div)
323  shift = (hscal - 1.0) * frq
324  newshift = att*shift
325  print("%10.3f %10.3f % 9.3f % 9.3f % 8.3f" % (frq, frq+newshift, shift, newshift, newshift-shift))
326  newfrq = frq+newshift
327  return newfrq
328  else:
329  return frq*hscal
330  elif frq <= div:
331  if lscal > 1.0:
332  # below = (div-frq)
333  # maxshf = (div-div/lscal)
334  # att = 1.0/(1.0 + maxshf/below)
335  att = (frq-div)/(frq-lscal*div)
336  shift = (lscal - 1.0) * frq
337  newshift = att*shift
338  print("%10.3f %10.3f % 9.3f % 9.3f % 8.3f" % (frq, frq+newshift, shift, newshift, newshift-shift))
339  newfrq = frq+newshift
340  return newfrq
341  else:
342  return frq*lscal
343  return np.array([scale_one(i) for i in arr])
344 
345 def read_frq_gen(fout):
346  ln = 0
347  for line in open(fout):
348  if 'TeraChem' in line:
349  return read_frq_tc(fout)
350  elif 'Q-Chem' in line:
351  return read_frq_qc(fout)
352  elif 'PSI4' in line:
353  return read_frq_psi(fout)
354  elif 'Gaussian' in line:
355  return read_frq_gau(fout)
356  elif 'ForceBalance' in line:
357  return read_frq_fb(fout)
358  ln += 1
359  raise RuntimeError('Cannot determine format')
360 
361 def main():
362  Mqc = Molecule(sys.argv[2])
363  psifrqs, psimodes, _, __, ___ = read_frq_gen(sys.argv[1])
364  qcfrqs, qcmodes, _, __, ___ = read_frq_gen(sys.argv[2])
365  gaufrqs, gaumodes, _, __, ___ = read_frq_gen(sys.argv[3])
366  for i, j, ii, jj, iii, jjj in zip(psifrqs, psimodes, qcfrqs, qcmodes, gaufrqs, gaumodes):
367  print("PsiFreq:", i, "QCFreq", ii, "GauFreq", iii)
368  print("PsiMode:", np.linalg.norm(j))
369  print_mode(Mqc, j)
370  print("QCMode:", np.linalg.norm(jj))
371  if np.linalg.norm(j - jj) < np.linalg.norm(j + jj):
372  print_mode(Mqc, jj)
373  else:
374  print_mode(Mqc, -1 * jj)
375  print("GauMode:", np.linalg.norm(jjj))
376  if np.linalg.norm(j - jjj) < np.linalg.norm(j + jjj):
377  print_mode(Mqc, jjj)
378  else:
379  print_mode(Mqc, -1 * jjj)
380 
381  print("DMode (QC-Gau):", end=' ')
382  if np.linalg.norm(jj - jjj) < np.linalg.norm(jj + jjj):
383  print(np.linalg.norm(jj - jjj))
384  print_mode(Mqc, jj - jjj)
385  else:
386  print(np.linalg.norm(jj + jjj))
387  print_mode(Mqc, jj + jjj)
388 
389  print("DMode (QC-Psi):", end=' ')
390  if np.linalg.norm(jj - j) < np.linalg.norm(jj + j):
391  print(np.linalg.norm(jj - j))
392  print_mode(Mqc, jj - j)
393  else:
394  print(np.linalg.norm(jj + j))
395  print_mode(Mqc, jj + j)
396 
397 if __name__ == "__main__":
398  main()
def read_frq_tc(tcout, scrdir='scr')
Definition: readfrq.py:74
Lee-Ping&#39;s general file format conversion class.
Definition: molecule.py:1182
def read_frq_psi(psiout)
Definition: readfrq.py:202
def isint(word)
ONLY matches integers! If you have a decimal point? None shall pass!
Definition: molecule.py:401
def main()
Definition: readfrq.py:364
def read_frq_gau(gauout)
Definition: readfrq.py:18
def read_frq_qc(qcout)
Definition: readfrq.py:142
def scale_freqs(arr)
Apply harmonic vibrational scaling factors.
Definition: readfrq.py:297
def read_frq_gen(fout)
Definition: readfrq.py:348
def isfloat(word)
Matches ANY number; it can be a decimal, scientific notation, integer, or what have you...
Definition: molecule.py:406
def read_frq_fb(vfnm)
Read ForceBalance-formatted vibrational data from a vdata.txt file.
Definition: readfrq.py:253
def print_mode(M, mode)
Definition: readfrq.py:15