ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
binding.py
Go to the documentation of this file.
1 """ @package forcebalance.binding Binding 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 import os
10 import shutil
11 import numpy as np
12 from forcebalance.nifty import col, eqcgmx, flat, floatornan, fqcgmx, invert_svd, kb, printcool, printcool_dictionary, bohr2ang, warn_press_key
13 from forcebalance.target import Target
14 from forcebalance.molecule import Molecule, format_xyz_coord
15 import re
16 import subprocess
17 from subprocess import PIPE
18 from forcebalance.finite_difference import fdwrap, f1d2p, f12d3p, in_fd
19 from collections import OrderedDict
20 from multiprocessing import Pool
21 
22 from forcebalance.output import getLogger
23 logger = getLogger(__name__)
24 
25 def parse_interactions(input_file):
26  """ Parse through the interactions input file.
27 
28  @param[in] input_file The name of the input file.
29 
30  """
31  # Three dictionaries of return variables.
32  Systems = OrderedDict()
33  Interactions = OrderedDict()
34  Globals = {}
35  InterNum = 0
36  InterName = "I0"
37  InterDict = {}
38  SystemName = None
39  SystemDict = {}
40 
41  logger.info("Reading interactions from file: %s\n" % input_file)
42  section = "NONE"
43  fobj = open(input_file).readlines()
44  for ln, line in enumerate(fobj):
45  # Anything after "#" is a comment
46  line = line.split("#")[0].strip()
47  s = line.split()
48  # Skip over blank lines
49  if len(s) == 0:
50  continue
51  key = s[0].lower()
52  # If line starts with a $, this signifies that we're in a new section.
53  if re.match('^\$',line):
54  word = re.sub('^\$','',line).upper()
55  if word == "END": # End of a section, time to reinitialize variables.
56  if section == "GLOBAL": pass
57  elif section == "SYSTEM":
58  if SystemName is None:
59  warn_press_key("You need to specify a name for the system on line %i" % ln)
60  elif SystemName in Systems:
61  warn_press_key("A system named %s already exists in Systems" % SystemName)
62  Systems[SystemName] = SystemDict
63  SystemName = None
64  SystemDict = {}
65  elif section == "INTERACTION":
66  if InterName in InterDict:
67  warn_press_key("A system named %s already exists in InterDict" % InterName)
68  Interactions[InterName] = InterDict
69  InterNum += 1
70  InterName = "I%i" % InterNum
71  InterDict = {}
72  else:
73  warn_press_key("Encountered $end for unsupported section %s on line %i" % (word, ln))
74  section = "NONE"
75  elif section == "NONE":
76  section = word
77  else:
78  warn_press_key("Encountered section keyword %s when already in section %s" % (word, section))
79  elif section == "GLOBAL":
80  if key in ['keyfile', 'energy_unit']:
81  Globals[key] = s[1]
82  elif key == 'optimize':
83  if len(s) == 1 or s[1].lower() in ['y','yes','true']:
84  logger.info("Optimizing ALL systems by default\n")
85  Globals[key] = True
86  else:
87  Globals[key] = False
88  else:
89  warn_press_key("Encountered unsupported key %s in section %s on line %i" % (key, section, ln))
90  elif section == "SYSTEM":
91  if key == 'name':
92  SystemName = s[1]
93  elif key == 'geometry':
94  SystemDict[key] = s[1]
95  elif key == 'rmsd_weight':
96  SystemDict[key] = float(s[1])
97  elif key == 'select':
98  SystemDict[key] = s[1]
99  elif key == 'optimize':
100  if len(s) == 1 or s[1].lower() in ['y','yes','true']:
101  SystemDict[key] = True
102  logger.info("Optimizing system %s\n" % SystemName)
103  else:
104  SystemDict[key] = False
105  else:
106  warn_press_key("Encountered unsupported key %s in section %s on line %i" % (key, section, ln))
107  elif section == "INTERACTION":
108  if key == 'name':
109  InterName = s[1]
110  elif key == 'equation':
111  InterDict[key] = ' '.join(s[1:])
112  elif key == 'energy':
113  InterDict[key] = float(s[1])
114  elif key == 'weight':
115  InterDict[key] = float(s[1])
116  else:
117  warn_press_key("Encountered unsupported key %s in section %s on line %i" % (key, section, ln))
118  return Globals, Systems, Interactions
119 
120 class BindingEnergy(Target):
121 
122  """ Improved subclass of Target for fitting force fields to binding energies. """
123 
124  def __init__(self,options,tgt_opts,forcefield):
125  super(BindingEnergy,self).__init__(options,tgt_opts,forcefield)
126  self.set_option(None, None, 'inter_txt', os.path.join(self.tgtdir,tgt_opts['inter_txt']))
127  self.global_opts, self.sys_opts, self.inter_opts = parse_interactions(self.inter_txt)
128  # If the global option doesn't exist in the system / interaction, then it is copied over.
129  for opt in self.global_opts:
130  for sys in self.sys_opts:
131  if opt not in self.sys_opts[sys]:
132  self.sys_opts[sys][opt] = self.global_opts[opt]
133  for inter in self.inter_opts:
134  if opt not in self.inter_opts[inter]:
135  self.inter_opts[inter][opt] = self.global_opts[opt]
136  for inter in self.inter_opts:
137  if 'energy_unit' in self.inter_opts[inter] and self.inter_opts[inter]['energy_unit'].lower() not in ['kilocalorie_per_mole', 'kilocalories_per_mole']:
138  logger.error('Usage of physical units is has been removed, please provide all binding energies in kcal/mole\n')
139  raise RuntimeError
140  self.inter_opts[inter]['reference_physical'] = self.inter_opts[inter]['energy']
141 
142  if tgt_opts['energy_denom'] == 0.0:
143  self.set_option(None, None, 'energy_denom', val=np.std(np.array([val['reference_physical'] for val in self.inter_opts.values()])))
144  else:
145  self.set_option(None, None, 'energy_denom', val=tgt_opts['energy_denom'])
146 
147  self.set_option(None, None, 'rmsd_denom', val=tgt_opts['rmsd_denom'])
148 
149  self.set_option(tgt_opts,'cauchy')
150  self.set_option(tgt_opts,'attenuate')
151 
153  self.loop_over_snapshots = False
154 
155  logger.info("The energy denominator is: %s\n" % str(self.energy_denom))
156  logger.info("The RMSD denominator is: %s\n" % str(self.rmsd_denom))
157 
158  if self.cauchy:
159  logger.info("Each contribution to the interaction energy objective function will be scaled by 1.0 / ( denom**2 + reference**2 )\n")
160  if self.attenuate:
161  logger.error('attenuate and cauchy are mutually exclusive\n')
162  raise RuntimeError
163  elif self.attenuate:
164  logger.info("Repulsive interactions beyond energy_denom will be scaled by 1.0 / ( denom**2 + (reference-denom)**2 )\n")
165 
166  engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items()))
167  engine_args.pop('name', None)
168 
169  self.engines = OrderedDict()
170  for sysname,sysopt in self.sys_opts.items():
171  M = Molecule(os.path.join(self.root, self.tgtdir, sysopt['geometry']))
172  if 'select' in sysopt:
173  atomselect = np.array(uncommadash(sysopt['select']))
174  M = M.atom_select(atomselect)
175  if self.FF.rigid_water: M.rigid_water()
176  self.engines[sysname] = self.engine_(target=self, mol=M, name=sysname, tinker_key=os.path.join(sysopt['keyfile']), **engine_args)
177 
178  def system_driver(self, sysname):
179  opts = self.sys_opts[sysname]
180  return self.engines[sysname].energy_rmsd(optimize = (opts['optimize'] if 'optimize' in opts else False))
182  def indicate(self):
183  printcool_dictionary(self.PrintDict,title="Interaction Energies (kcal/mol), Objective = % .5e\n %-20s %9s %9s %9s %11s" %
184  (self.energy_part, "Interaction", "Calc.", "Ref.", "Delta", "Term"))
185  if len(self.RMSDDict) > 0:
186  printcool_dictionary(self.RMSDDict,title="Geometry Optimized Systems (Angstrom), Objective = %.5e\n %-38s %11s %11s" % (self.rmsd_part, "System", "RMSD", "Term"), keywidth=45)
187 
188  def get(self, mvals, AGrad=False, AHess=False):
189  Answer = {'X':0.0, 'G':np.zeros(self.FF.np), 'H':np.zeros((self.FF.np, self.FF.np))}
190  self.PrintDict = OrderedDict()
191  self.RMSDDict = OrderedDict()
192  EnergyDict = OrderedDict()
193  #pool = Pool(processes=4)
194  def compute(mvals_):
195  # This function has automatically assigned variable names from the interaction master file
196  # Thus, all variable names in here are protected using an underscore.
197  self.FF.make(mvals_)
198  VectorD_ = []
199  for sys_ in self.sys_opts:
200  Energy_, RMSD_ = self.system_driver(sys_)
201  # Energies are stored in a dictionary.
202  EnergyDict[sys_] = Energy_
203  RMSDNrm_ = RMSD_ / self.rmsd_denom
204  w_ = self.sys_opts[sys_]['rmsd_weight'] if 'rmsd_weight' in self.sys_opts[sys_] else 1.0
205  VectorD_.append(np.sqrt(w_)*RMSDNrm_)
206  if not in_fd() and RMSD_ != 0.0:
207  self.RMSDDict[sys_] = "% 9.3f % 12.5f" % (RMSD_, w_*RMSDNrm_**2)
208  VectorE_ = []
209  for inter_ in self.inter_opts:
210  def encloseInDictionary(matchobj):
211  return 'EnergyDict["' + matchobj.group(0)+'"]'
212  # Here we need to evaluate a mathematical expression of the stored variables in EnergyDict.
213  # We start by enclosing every variable in EnergyDict[""] and then calling eval on it.
214  evalExpr = re.sub('[A-Za-z_][A-Za-z0-9_]*', encloseInDictionary, self.inter_opts[inter_]['equation'])
215  Calculated_ = eval(evalExpr)
216  Reference_ = self.inter_opts[inter_]['reference_physical']
217  Delta_ = Calculated_ - Reference_
218  Denom_ = self.energy_denom
219  if self.cauchy:
220  Divisor_ = np.sqrt(Denom_**2 + Reference_**2)
221  elif self.attenuate:
222  if Reference_ < Denom_:
223  Divisor_ = Denom_
224  else:
225  Divisor_ = np.sqrt(Denom_**2 + (Reference_-Denom_)**2)
226  else:
227  Divisor_ = Denom_
228  DeltaNrm_ = Delta_ / Divisor_
229  w_ = self.inter_opts[inter_]['weight'] if 'weight' in self.inter_opts[inter_] else 1.0
230  VectorE_.append(np.sqrt(w_)*DeltaNrm_)
231  if not in_fd():
232  self.PrintDict[inter_] = "% 9.3f % 9.3f % 9.3f % 12.5f" % (Calculated_, Reference_, Delta_, w_*DeltaNrm_**2)
233  # print "%-20s" % inter_, "Calculated:", Calculated_, "Reference:", Reference_, "Delta:", Delta_, "DeltaNrm:", DeltaNrm_
234  # The return value is an array of normalized interaction energy differences.
235  if not in_fd():
236  self.rmsd_part = np.dot(np.array(VectorD_),np.array(VectorD_))
237  if len(VectorE_) > 0:
238  self.energy_part = np.dot(np.array(VectorE_),np.array(VectorE_))
239  else:
240  self.energy_part = 0.0
241  if len(VectorE_) > 0 and len(VectorD_) > 0:
242  return np.array(VectorD_ + VectorE_)
243  elif len(VectorD_) > 0:
244  return np.array(VectorD_)
245  elif len(VectorE_) > 0:
246  return np.array(VectorE_)
247 
248  V = compute(mvals)
249 
250  dV = np.zeros((self.FF.np,len(V)))
251  if AGrad or AHess:
252  for p in self.pgrad:
253  dV[p,:], _ = f12d3p(fdwrap(compute, mvals, p), h = self.h, f0 = V)
254 
255  Answer['X'] = np.dot(V,V)
256  for p in self.pgrad:
257  Answer['G'][p] = 2*np.dot(V, dV[p,:])
258  for q in self.pgrad:
259  Answer['H'][p,q] = 2*np.dot(dV[p,:], dV[q,:])
260 
261  if not in_fd():
262  self.objective = Answer['X']
263  self.FF.make(mvals)
264 
265  return Answer
Nifty functions, intended to be imported by any module within ForceBalance.
engines
Build keyword dictionaries to pass to engine.
Definition: binding.py:172
def __init__(self, options, tgt_opts, forcefield)
Definition: binding.py:127
def in_fd()
Invoking this function from anywhere will tell us whether we&#39;re being called by a finite-difference f...
def system_driver(self, sysname)
Definition: binding.py:181
def fdwrap(func, mvals0, pidx, key=None, kwargs)
A function wrapper for finite difference designed for differentiating &#39;get&#39;-type functions.
def warn_press_key(warning, timeout=10)
Definition: nifty.py:1599
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
def get(self, mvals, AGrad=False, AHess=False)
Definition: binding.py:191
def parse_interactions(input_file)
Parse through the interactions input file.
Definition: binding.py:32
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
def uncommadash(s)
Definition: nifty.py:249
Improved subclass of Target for fitting force fields to binding energies.
Definition: binding.py:124
loop_over_snapshots
LPW 2018-02-11: This is set to True if the target calculates a single-point property over several exi...
Definition: binding.py:156