ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
vibration.py
Go to the documentation of this file.
1 """ @package forcebalance.vibration Vibrational mode fitting module
2 
3 @author Lee-Ping Wang
4 @date 08/2012
5 """
6 from __future__ import division
7 
8 from builtins import zip
9 from builtins import range
10 import os
11 import shutil
12 from forcebalance.nifty import col, eqcgmx, flat, floatornan, fqcgmx, invert_svd, kb, printcool, bohr2ang, warn_press_key, pvec1d, pmat2d
13 import numpy as np
14 from forcebalance.target import Target
15 from forcebalance.molecule import Molecule, format_xyz_coord
16 from re import match, sub
17 import subprocess
18 from subprocess import PIPE
19 from forcebalance.finite_difference import fdwrap, f1d2p, f12d3p, in_fd
20 # from ._assign import Assign
21 from scipy import optimize
22 from collections import OrderedDict
23 #from _increment import Vibration_Build
24 
25 from forcebalance.output import getLogger
26 logger = getLogger(__name__)
27 
28 def count_assignment(assignment, verbose=True):
29  for i in range(len(assignment)):
30  if sum(assignment==i) != 1 and verbose:
31  logger.info("Vibrational mode %i is assigned %i times\n" % (i+1, sum(assignment==i)))
32 
33 class Vibration(Target):
34 
35  """ Subclass of Target for fitting force fields to vibrational spectra (from experiment or theory).
36 
37  Currently Tinker is supported.
38 
39  """
40 
41  def __init__(self,options,tgt_opts,forcefield):
42  """Initialization."""
43 
44  # Initialize the SuperClass!
45  super(Vibration,self).__init__(options,tgt_opts,forcefield)
46 
47  #======================================#
48  # Options that are given by the parser #
49  #======================================#
50  self.set_option(tgt_opts,'wavenumber_tol','denom')
51  self.set_option(tgt_opts,'reassign_modes','reassign')
52  self.set_option(tgt_opts,'normalize')
53 
54  #======================================#
55  # Variables which are set here #
56  #======================================#
57 
59  self.loop_over_snapshots = False
60 
61  self.vfnm = os.path.join(self.tgtdir,"vdata.txt")
62 
63  self.read_reference_data()
64 
65  engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items()))
66  engine_args.pop('name', None)
67 
68  self.engine = self.engine_(target=self, **engine_args)
69  if self.FF.rigid_water:
70  logger.error('This class cannot be used with rigid water molecules.\n')
71  raise RuntimeError
72 
73  def read_reference_data(self):
74  """ Read the reference vibrational data from a file. """
75 
76  self.na = -1
77  self.ref_eigvals = []
78  self.ref_eigvecs = []
79  an = 0
80  ln = 0
81  cn = -1
82  for line in open(self.vfnm):
83  line = line.split('#')[0] # Strip off comments
84  s = line.split()
85  if len(s) == 1 and self.na == -1:
86  self.na = int(s[0])
87  xyz = np.zeros((self.na, 3))
88  cn = ln + 1
89  elif ln == cn:
90  pass
91  elif an < self.na and len(s) == 4:
92  xyz[an, :] = np.array([float(i) for i in s[1:]])
93  an += 1
94  elif len(s) == 1:
95  if float(s[0]) < 0:
96  logger.warning('Warning: Setting imaginary frequency = % .3fi to zero.\n' % abs(float(s[0])))
97  self.ref_eigvals.append(0.0)
98  else:
99  self.ref_eigvals.append(float(s[0]))
100  self.ref_eigvecs.append(np.zeros((self.na, 3)))
101  an = 0
102  elif len(s) == 3:
103  self.ref_eigvecs[-1][an, :] = np.array([float(i) for i in s])
104  an += 1
105  elif len(s) == 0:
106  pass
107  else:
108  logger.info(line + '\n')
109  logger.error("This line doesn't comply with our vibration file format!\n")
110  raise RuntimeError
111  ln += 1
112  self.ref_eigvals = np.array(self.ref_eigvals)
113  self.ref_eigvecs = np.array(self.ref_eigvecs)
114  for v2 in self.ref_eigvecs:
115  v2 /= np.linalg.norm(v2)
116  return
117 
118  def indicate(self):
119  """ Print qualitative indicator. """
120  if self.reassign == 'overlap' : count_assignment(self.c2r)
121  banner = "Frequencies (wavenumbers)"
122  headings = ["Mode #", "Reference", "Calculated", "Difference", "Ref(dot)Calc"]
123  data = OrderedDict([(i, ["%.4f" % self.ref_eigvals[i], "%.4f" % self.calc_eigvals[i], "%.4f" % (self.calc_eigvals[i] - self.ref_eigvals[i]), "%.4f" % self.overlaps[i]]) for i in range(len(self.ref_eigvals))])
124  self.printcool_table(data, headings, banner)
125  return
126 
127  def vibration_driver(self):
128  if hasattr(self, 'engine') and hasattr(self.engine, 'normal_modes'):
129  return self.engine.normal_modes()
130  else:
131  logger.error('Normal mode calculation not supported, try using a different engine\n')
132  raise NotImplementedError
133 
134  def vib_overlap(self, v1, v2):
135  """
136  Calculate overlap between vibrational modes for two Cartesian displacements.
137 
138  Parameters
139  ----------
140  v1, v2 : np.ndarray
141  The two sets of Cartesian displacements to compute overlap for,
142  3*N_atoms values each.
143 
144  Returns
145  -------
146  float
147  Overlap between mass-weighted eigenvectors
148  """
149  sqrtm = np.sqrt(np.array(self.engine.AtomLists['Mass']))
150  v1m = v1.copy()
151  v1m *= sqrtm[:, np.newaxis]
152  v1m /= np.linalg.norm(v1m)
153  v2m = v2.copy()
154  v2m *= sqrtm[:, np.newaxis]
155  v2m /= np.linalg.norm(v2m)
156  return np.abs(np.dot(v1m.flatten(), v2m.flatten()))
157 
158  def get(self, mvals, AGrad=False, AHess=False):
159  """ Evaluate objective function. """
160  Answer = {'X':0.0, 'G':np.zeros(self.FF.np), 'H':np.zeros((self.FF.np, self.FF.np))}
161 
162  def get_eigvals(mvals_):
163  self.FF.make(mvals_)
164  eigvals, eigvecs = self.vibration_driver()
165  # The overlap metric may take into account some frequency differences.
166  # Here, an element of dev is equal to 2/3 if (for example) the frequencies differ by 1000.
167  dev = np.array([[(np.abs(i-j)/1000)/(1.0+np.abs(i-j)/1000) for j in self.ref_eigvals] for i in eigvals])
168  for i in range(dev.shape[0]):
169  dev[i, :] /= max(dev[i, :])
170 
171  if self.reassign in ['permute', 'overlap']:
172  # The elements of "a" matrix are the column numbers (reference mode numbers)
173  # that are mapped to the row numbers (calculated mode numbers).
174  # Highly similar eigenvectors are assigned small values because
175  # the assignment problem is a cost minimization problem.
176  a = np.array([[(1.0-self.vib_overlap(v1, v2)) for v2 in self.ref_eigvecs] for v1 in eigvecs])
177  a += dev
178  if self.reassign == 'permute':
179  row, c2r = optimize.linear_sum_assignment(a)
180  eigvals = eigvals[c2r]
181  elif self.reassign == 'overlap':
182  c2r = np.argmin(a, axis=0)
183  eigvals_p = []
184  for j in c2r:
185  eigvals_p.append(eigvals[j])
186  eigvals = np.array(eigvals_p)
187  if not in_fd():
188  if self.reassign == 'permute':
189  eigvecs = eigvecs[c2r]
190  elif self.reassign == 'overlap':
191  self.c2r = c2r
192  eigvecs_p = []
193  for j in c2r:
194  eigvecs_p.append(eigvecs[j])
195  eigvecs = np.array(eigvecs_p)
196  self.overlaps = np.array([self.vib_overlap(v1, v2) for v1, v2 in zip(self.ref_eigvecs, eigvecs)])
197  return eigvals
199  calc_eigvals = get_eigvals(mvals)
200  D = calc_eigvals - self.ref_eigvals
201  dV = np.zeros((self.FF.np,len(calc_eigvals)))
202  if AGrad or AHess:
203  for p in self.pgrad:
204  dV[p,:], _ = f12d3p(fdwrap(get_eigvals, mvals, p), h = self.h, f0 = calc_eigvals)
205  Answer['X'] = np.dot(D,D) / self.denom**2 / (len(D) if self.normalize else 1)
206  for p in self.pgrad:
207  Answer['G'][p] = 2*np.dot(D, dV[p,:]) / self.denom**2 / (len(D) if self.normalize else 1)
208  for q in self.pgrad:
209  Answer['H'][p,q] = 2*np.dot(dV[p,:], dV[q,:]) / self.denom**2 / (len(D) if self.normalize else 1)
210  if not in_fd():
211  self.calc_eigvals = calc_eigvals
212  self.objective = Answer['X']
213  return Answer
def count_assignment(assignment, verbose=True)
Definition: vibration.py:29
loop_over_snapshots
LPW 2018-02-11: This is set to True if the target calculates a single-point property over several exi...
Definition: vibration.py:62
Nifty functions, intended to be imported by any module within ForceBalance.
def vib_overlap(self, v1, v2)
Calculate overlap between vibrational modes for two Cartesian displacements.
Definition: vibration.py:154
def in_fd()
Invoking this function from anywhere will tell us whether we&#39;re being called by a finite-difference f...
def fdwrap(func, mvals0, pidx, key=None, kwargs)
A function wrapper for finite difference designed for differentiating &#39;get&#39;-type functions.
na
Number of atoms.
Definition: vibration.py:80
def __init__(self, options, tgt_opts, forcefield)
Initialization.
Definition: vibration.py:45
def indicate(self)
Print qualitative indicator.
Definition: vibration.py:124
def read_reference_data(self)
Read the reference vibrational data from a file.
Definition: vibration.py:78
Subclass of Target for fitting force fields to vibrational spectra (from experiment or theory)...
Definition: vibration.py:40
def get(self, mvals, AGrad=False, AHess=False)
Evaluate objective function.
Definition: vibration.py:166
def vibration_driver(self)
Definition: vibration.py:132
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
vfnm
The vdata.txt file that contains the vibrations.
Definition: vibration.py:64
engine
Read in the reference data.
Definition: vibration.py:71