ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
interaction.py
Go to the documentation of this file.
1 """ @package forcebalance.interaction Interaction energy fitting module.
2 
3 @author Lee-Ping Wang
4 @date 05/2012
5 """
6 from __future__ import division
7 
8 from builtins import str
9 from builtins import range
10 import os
11 import shutil
12 import numpy as np
13 from forcebalance.nifty import col, eqcgmx, flat, floatornan, fqcgmx, invert_svd, kb, printcool, bohr2ang, commadash, uncommadash, printcool_dictionary
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 collections import OrderedDict
21 
22 from forcebalance.output import getLogger
23 logger = getLogger(__name__)
24 
25 class Interaction(Target):
26 
27  """ Subclass of Target for fitting force fields to interaction energies.
28 
29  Currently TINKER is supported.
30 
31  We introduce the following concepts:
32  - The number of snapshots
33  - The reference interaction energies and the file they belong in (qdata.txt)
34 
35  This subclass contains the 'get' method for building the objective
36  function from any simulation software (a driver to run the program and
37  read output is still required)."""
38 
39  def __init__(self,options,tgt_opts,forcefield):
40  # Initialize the SuperClass!
41  super(Interaction,self).__init__(options,tgt_opts,forcefield)
42 
43  #======================================#
44  # Options that are given by the parser #
45  #======================================#
46 
47 
48  self.set_option(tgt_opts,'shots','ns')
49 
50  self.set_option(tgt_opts,'do_cosmo','do_cosmo')
51 
52  self.set_option(tgt_opts,'cauchy','cauchy')
53 
54  self.set_option(tgt_opts,'attenuate','attenuate')
55 
56  self.set_option(tgt_opts, 'normalize')
57 
58  self.set_option(tgt_opts,'energy_denom','energy_denom')
59 
60  self.set_option(tgt_opts,'fragment1','fragment1')
61  if len(self.fragment1) == 0:
62  logger.error('You need to define the first fragment using the fragment1 keyword\n')
63  raise RuntimeError
64  self.select1 = np.array(uncommadash(self.fragment1))
65 
66  self.set_option(tgt_opts,'fragment2','fragment2')
67  if len(self.fragment2) != 0:
68  self.select2 = np.array(uncommadash(self.fragment2))
69  else:
70  self.select2 = None
71 
72  self.set_option(tgt_opts,'energy_upper','energy_upper')
73 
74  self.set_option(tgt_opts,'writelevel','writelevel')
75  #======================================#
76  # Variables which are set here #
77  #======================================#
78 
80  self.loop_over_snapshots = True
81 
82  self.eqm = []
83 
84  self.label = []
85 
86  self.qfnm = os.path.join(self.tgtdir,"qdata.txt")
87  self.e_err = 0.0
88  self.e_err_pct = None
89 
90  self.mol = Molecule(os.path.join(self.root,self.tgtdir,self.coords),
91  top=(os.path.join(self.root,self.tgtdir,self.pdb) if hasattr(self, 'pdb') else None), build_topology=False if self.coords.endswith('.pdb') else True)
92  if self.ns != -1:
93  self.mol = self.mol[:self.ns]
94  self.ns = len(self.mol)
95  if self.select2 is None:
96  self.select2 = [i for i in range(self.mol.na) if i not in self.select1]
97  logger.info('Fragment 2 is the complement of fragment 1 : %s\n' % (commadash(self.select2)))
98 
99  engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items()))
100  engine_args.pop('name', None)
101  self.engine = self.engine_(target=self, mol=self.mol, **engine_args)
102 
104  logger.info("The energy denominator is: %s kcal/mol\n" % str(self.energy_denom))
105  denom = self.energy_denom
106  # Create the denominator.
107  if self.cauchy:
108  self.divisor = np.sqrt(self.eqm**2 + denom**2)
109  if self.attenuate:
110  logger.error('attenuate and cauchy are mutually exclusive\n')
111  raise RuntimeError
112  elif self.attenuate:
113  # Attenuate only large repulsions.
114  self.divisor = np.zeros(len(self.eqm))
115  for i in range(len(self.eqm)):
116  if self.eqm[i] < denom:
117  self.divisor[i] = denom
118  else:
119  self.divisor[i] = np.sqrt(denom**2 + (self.eqm[i]-denom)**2)
120  else:
121  self.divisor = np.ones(len(self.eqm)) * denom
122 
123  if self.cauchy:
124  logger.info("Each contribution to the interaction energy objective function will be scaled by 1.0 / ( energy_denom**2 + reference**2 )\n")
125  if self.energy_upper > 0:
126  ecut = self.energy_upper
127  self.prefactor = 1.0 * (self.eqm < ecut)
128  logger.info("Interactions more repulsive than %s will not be fitted (%i/%i excluded) \n" % (str(self.energy_upper), sum(self.eqm > ecut), len(self.eqm)))
129  else:
130  self.prefactor = np.ones(len(self.eqm))
131  if self.normalize:
132  self.prefactor /= len(self.prefactor)
133 
134  def read_reference_data(self):
135 
136  """ Read the reference ab initio data from a file such as qdata.txt.
137 
138  After reading in the information from qdata.txt, it is converted
139  into kcal/mol.
140 
141  """
142  # Parse the qdata.txt file
143  for line in open(os.path.join(self.root,self.qfnm)):
144  sline = line.split()
145  if len(sline) == 0: continue
146  elif sline[0] == 'INTERACTION':
147  self.eqm.append(float(sline[1]))
148  elif sline[0] == 'LABEL':
149  self.label.append(sline[1])
150  if all(len(i) in [self.ns, 0] for i in [self.eqm]) and len(self.eqm) == self.ns:
151  break
152  self.ns = len(self.eqm)
153  # Turn everything into arrays, convert to kcal/mol
154  self.eqm = np.array(self.eqm)
155  self.eqm *= (eqcgmx / 4.184)
156 
157  def indicate(self):
158  delta = (self.emm-self.eqm)
159  deltanrm = self.prefactor*(delta/self.divisor)**2
160  if len(self.label) == self.ns:
161  PrintDict = OrderedDict()
162  for i,label in enumerate(self.label):
163  PrintDict[label] = "% 9.3f % 9.3f % 9.3f % 9.3f % 11.5f" % (self.emm[i], self.eqm[i], delta[i], self.divisor[i], deltanrm[i])
164  printcool_dictionary(PrintDict,title="Target: %s\nInteraction Energies (kcal/mol), Objective = % .5e\n %-10s %9s %9s %9s %9s %11s" %
165  (self.name, self.objective, "Label", "Calc.", "Ref.", "Delta", "Divisor", "Term"),keywidth=15)
166  else:
167  # logger.info("Target: %s Objective: % .5e (add LABEL keywords in qdata.txt for full printout)\n" % (self.name,self.objective))
168  Headings = ["Observable", "Difference\nRMS (Calc-Ref)", "Denominator\n(Specified)", " Percent \nDifference"]
169  Data = OrderedDict([])
170  Data['Energy (kcal/mol)'] = ["%8.4f" % np.sqrt(np.mean(delta**2)),
171  "%8.4f" % np.mean(self.divisor),
172  "%.4f%%" % (np.sqrt(np.mean(delta/self.divisor)**2)*100)]
173  self.printcool_table(data=Data, headings=Headings, color=0)
174  logger.info("add LABEL keywords in qdata.txt to print out each snapshot\n")
175 
176 
177  # if len(self.RMSDDict) > 0:x
178  # printcool_dictionary(self.RMSDDict,title="Geometry Optimized Systems (Angstrom), Objective = %.5e\n %-38s %11s %11s" % (self.rmsd_part, "System", "RMSD", "Term"), keywidth=45)
179 
180  def get(self, mvals, AGrad=False, AHess=False):
181  """ Evaluate objective function. """
182  Answer = {'X':0.0, 'G':np.zeros(self.FF.np), 'H':np.zeros((self.FF.np, self.FF.np))}
183 
184  # If the weight is zero, turn all derivatives off.
185  if (self.weight == 0.0):
186  AGrad = False
187  AHess = False
188 
189  def callM(mvals_, dielectric=False):
190  logger.info("\r")
191  pvals = self.FF.make(mvals_)
192  return self.engine.interaction_energy(self.select1, self.select2)
193 
194  logger.info("Executing\r")
195  emm = callM(mvals)
196 
197  D = emm - self.eqm
198  dV = np.zeros((self.FF.np,len(emm)))
199 
200  if self.writelevel > 0:
201  # Dump interaction energies to disk.
202  np.savetxt('M.txt',emm)
203  np.savetxt('Q.txt',self.eqm)
204  import pickle
205  pickle.dump((self.name, self.label, self.prefactor, self.eqm, emm), open("qm_vs_mm.p",'w'))
206  # select the qm and mm data that has >0 weight to plot
207  qm_data, mm_data = [], []
208  for i in range(len(self.eqm)):
209  if self.prefactor[i] != 0:
210  qm_data.append(self.eqm[i])
211  mm_data.append(emm[i])
212  plot_interaction_qm_vs_mm(qm_data, mm_data, title="Interaction Energy "+self.name)
213 
214  # Do the finite difference derivative.
215  if AGrad or AHess:
216  for p in self.pgrad:
217  dV[p,:], _ = f12d3p(fdwrap(callM, mvals, p), h = self.h, f0 = emm)
218  # Create the force field one last time.
219  pvals = self.FF.make(mvals)
220 
221  Answer['X'] = np.dot(self.prefactor*D/self.divisor,D/self.divisor)
222  for p in self.pgrad:
223  Answer['G'][p] = 2*np.dot(self.prefactor*D/self.divisor, dV[p,:]/self.divisor)
224  for q in self.pgrad:
225  Answer['H'][p,q] = 2*np.dot(self.prefactor*dV[p,:]/self.divisor, dV[q,:]/self.divisor)
226 
227  if not in_fd():
228  self.emm = emm
229  self.objective = Answer['X']
230 
231 
232  try:
233  if self.engine.name == 'openmm':
234  if hasattr(self.engine, 'simulation'): del self.engine.simulation
235  if hasattr(self.engine, 'A'): del self.engine.A
236  if hasattr(self.engine, 'B'): del self.engine.B
237  except:
238  pass
239 
240  return Answer
241 
242 def plot_interaction_qm_vs_mm(eqm, emm, title=''):
243  import matplotlib.pyplot as plt
244  plt.plot(eqm, label='QM Data', marker='^')
245  plt.plot(emm, label='MM Data', marker='o')
246  plt.legend()
247  plt.xlabel('Snapshots')
248  plt.ylabel('Interaction Energy (kcal/mol)')
249  plt.title(title)
250  plt.savefig("e_qm_vs_mm.pdf")
251  plt.close()
def plot_interaction_qm_vs_mm(eqm, emm, title='')
Definition: interaction.py:246
divisor
Read in the reference data.
Definition: interaction.py:110
Nifty functions, intended to be imported by any module within ForceBalance.
engine
Build keyword dictionaries to pass to engine.
Definition: interaction.py:103
mol
Read in the trajectory file.
Definition: interaction.py:92
select1
Number of snapshots.
Definition: interaction.py:66
eqm
Reference (QM) interaction energies.
Definition: interaction.py:84
def get(self, mvals, AGrad=False, AHess=False)
Evaluate objective function.
Definition: interaction.py:185
loop_over_snapshots
Set upper cutoff energy.
Definition: interaction.py:82
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.
qfnm
The qdata.txt file that contains the QM energies and forces.
Definition: interaction.py:88
def commadash(l)
Definition: nifty.py:239
label
Snapshot label, useful for graphing.
Definition: interaction.py:86
def printcool_dictionary(Dict, title="Dictionary Keys : Values", bold=False, color=2, keywidth=25, topwidth=50, center=True, leftpad=0)
See documentation for printcool; this is a nice way to print out keys/values in a dictionary...
Definition: nifty.py:366
select2
Set fragment 2.
Definition: interaction.py:70
def read_reference_data(self)
Read the reference ab initio data from a file such as qdata.txt.
Definition: interaction.py:143
def __init__(self, options, tgt_opts, forcefield)
Definition: interaction.py:41
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
Subclass of Target for fitting force fields to interaction energies.
Definition: interaction.py:38
def uncommadash(s)
Definition: nifty.py:249