ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
torsion_profile.py
Go to the documentation of this file.
1 """ @package forcebalance.torsion_profile Torsion profile fitting module.
2 
3 @author Lee-Ping Wang
4 @date 08/2019
5 """
6 from __future__ import division
7 import os
8 import numpy as np
9 import itertools
10 import json
11 from copy import deepcopy
12 from collections import OrderedDict
13 from forcebalance.nifty import eqcgmx, printcool, printcool_dictionary, warn_press_key
14 from forcebalance.target import Target
15 from forcebalance.molecule import Molecule
16 from forcebalance.finite_difference import fdwrap, f12d3p, in_fd
17 from forcebalance.output import getLogger
18 from forcebalance.optimizer import Counter
19 logger = getLogger(__name__)
20 
21 class TorsionProfileTarget(Target):
22  """ Subclass of Target for fitting MM optimized geometries to QM optimized geometries. """
23  def __init__(self, options, tgt_opts, forcefield):
24  super(TorsionProfileTarget, self).__init__(options, tgt_opts, forcefield)
25 
26  if not os.path.exists(os.path.join(self.tgtdir, 'metadata.json')):
27  raise RuntimeError('TorsionProfileTarget needs torsion drive metadata.json file')
28  with open(os.path.join(self.tgtdir, 'metadata.json')) as f:
29  self.metadata = json.load(f)
30  self.ndim = len(self.metadata['dihedrals'])
31  self.freeze_atoms = sorted(set(itertools.chain(*self.metadata['dihedrals'])))
32 
33 
34  if hasattr(self, 'pdb') and self.pdb is not None:
35  self.mol = Molecule(os.path.join(self.root,self.tgtdir,self.coords),
36  top=(os.path.join(self.root,self.tgtdir,self.pdb)))
37  else:
38  self.mol = Molecule(os.path.join(self.root,self.tgtdir,self.coords))
39 
40  self.ns = len(self.mol)
41 
42  self.set_option(tgt_opts,'writelevel','writelevel')
43 
44  self.set_option(tgt_opts,'restrain_k','restrain_k')
45 
46  self.set_option(tgt_opts,'attenuate','attenuate')
47 
48  self.set_option(tgt_opts,'energy_denom','energy_denom')
49 
50  self.set_option(tgt_opts,'energy_upper','energy_upper')
51 
52  self.read_reference_data()
53 
54  engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items()))
55  engine_args.pop('name', None)
56 
57  engine_args['freeze_atoms'] = self.freeze_atoms
58  self.engine = self.engine_(target=self, mol=self.mol, **engine_args)
59 
61 
62  """ Read the reference ab initio data from a file such as qdata.txt.
63 
64  After reading in the information from qdata.txt, it is converted
65  into the GROMACS energy units (kind of an arbitrary choice).
66  """
67 
68  self.eqm = []
69 
70  self.qfnm = os.path.join(self.tgtdir,"qdata.txt")
71  # Parse the qdata.txt file
72  for line in open(os.path.join(self.root,self.qfnm)):
73  sline = line.split()
74  if len(sline) == 0: continue
75  elif sline[0] == 'ENERGY':
76  self.eqm.append(float(sline[1]))
77 
78  if len(self.eqm) != self.ns:
79  raise RuntimeError("Length of qdata.txt should match number of structures")
80 
81  # Turn everything into arrays, convert to kcal/mol
82  self.eqm = np.array(self.eqm)
83  self.eqm *= eqcgmx / 4.184
84  # Use the minimum energy structure of the QM as reference
85  self.eqm -= np.min(self.eqm)
86  self.smin = np.argmin(self.eqm)
87  logger.info("Referencing all energies to the snapshot %i (minimum energy structure in QM)\n" % self.smin)
88 
89  if self.attenuate:
90  # Attenuate energies by an amount proportional to their
91  # value above the minimum.
92  eqm1 = self.eqm - np.min(self.eqm)
93  denom = self.energy_denom
94  upper = self.energy_upper
95  self.wts = np.ones(self.ns)
96  for i in range(self.ns):
97  if eqm1[i] > upper:
98  self.wts[i] = 0.0
99  elif eqm1[i] < denom:
100  self.wts[i] = 1.0 / denom
101  else:
102  self.wts[i] = 1.0 / np.sqrt(denom**2 + (eqm1[i]-denom)**2)
103  else:
104  self.wts = np.ones(self.ns)
105 
106  # Normalize weights.
107  self.wts /= sum(self.wts)
108 
109  def indicate(self):
110  title_str = "Torsion Profile: %s, Objective = % .5e, Units = kcal/mol, Angstrom" % (self.name, self.objective)
111  #LPW: This title is carefully placed to align correctly
112  column_head_str1 = "%-50s %-10s %-12s %-18s %-12s %-10s %-11s %-10s" % ("System", "Min(QM)", "Min(MM)", "Range(QM)", "Range(MM)", "Max-RMSD", "Ene-RMSE", "Obj-Fn")
113  printcool_dictionary(self.PrintDict,title=title_str + '\n' + column_head_str1, keywidth=50, center=[True,False])
114 
115  def get(self, mvals, AGrad=False, AHess=False):
116  Answer = {'X':0.0, 'G':np.zeros(self.FF.np), 'H':np.zeros((self.FF.np, self.FF.np))}
117  self.PrintDict = OrderedDict()
119  def compute(mvals_, indicate=False):
120  self.FF.make(mvals_)
121  M_opts = None
122  compute.emm = []
123  compute.rmsd = []
124  for i in range(self.ns):
125  energy, rmsd, M_opt = self.engine.optimize(shot=i, align=False)
126  # Create a molecule object to hold the MM-optimized structures
127  compute.emm.append(energy)
128  compute.rmsd.append(rmsd)
129  if M_opts is None:
130  M_opts = deepcopy(M_opt)
131  else:
132  M_opts += M_opt
133  compute.emm = np.array(compute.emm)
134  compute.emm -= compute.emm[self.smin]
135  compute.rmsd = np.array(compute.rmsd)
136  if indicate:
137  if self.writelevel > 0:
138  energy_comparison = np.array([
139  self.eqm,
140  compute.emm,
141  compute.emm - self.eqm,
142  np.sqrt(self.wts)/self.energy_denom
143  ]).T
144  np.savetxt("EnergyCompare.txt", energy_comparison, header="%11s %12s %12s %12s" % ("QMEnergy", "MMEnergy", "Delta(MM-QM)", "Weight"), fmt="% 12.6e")
145  M_opts.write('mm_minimized.xyz')
146  if self.ndim == 1:
147  try:
148  import matplotlib.pyplot as plt
149  plt.switch_backend('agg')
150  fig, ax = plt.subplots()
151  dihedrals = np.array([i[0] for i in self.metadata['torsion_grid_ids']])
152  dsort = np.argsort(dihedrals)
153  ax.plot(dihedrals[dsort], self.eqm[dsort], label='QM')
154  if hasattr(self, 'emm_orig'):
155  ax.plot(dihedrals[dsort], compute.emm[dsort], label='MM Current')
156  ax.plot(dihedrals[dsort], self.emm_orig[dsort], label='MM Initial')
157  else:
158  ax.plot(dihedrals[dsort], compute.emm[dsort], label='MM Initial')
159  self.emm_orig = compute.emm.copy()
160  ax.legend()
161  ax.set_xlabel('Dihedral (degree)')
162  ax.set_ylabel('Energy (kcal/mol)')
163  fig.suptitle('Torsion profile: iteration %i\nSystem: %s' % (Counter(), self.name))
164  fig.savefig('plot_torsion.pdf')
165  except ImportError:
166  logger.warning("matplotlib package is needed to make torsion profile plots\n")
167  return (np.sqrt(self.wts)/self.energy_denom) * (compute.emm - self.eqm)
168  compute.emm = None
169  compute.rmsd = None
170 
171  V = compute(mvals, indicate=True)
172 
173  Answer['X'] = np.dot(V,V)
174 
175  # Energy RMSE
176  e_rmse = np.sqrt(np.dot(self.wts, (compute.emm - self.eqm)**2))
177 
178  self.PrintDict[self.name] = '%10s %10s %6.3f - %-6.3f % 6.3f - %-6.3f %6.3f %7.4f % 7.4f' % (','.join(['%i' % i for i in self.metadata['torsion_grid_ids'][self.smin]]),
179  ','.join(['%i' % i for i in self.metadata['torsion_grid_ids'][np.argmin(compute.emm)]]),
180  min(self.eqm), max(self.eqm), min(compute.emm), max(compute.emm), max(compute.rmsd), e_rmse, Answer['X'])
181 
182  # compute gradients and hessian
183  dV = np.zeros((self.FF.np,len(V)))
184  if AGrad or AHess:
185  for p in self.pgrad:
186  dV[p,:], _ = f12d3p(fdwrap(compute, mvals, p), h = self.h, f0 = V)
187 
188  for p in self.pgrad:
189  Answer['G'][p] = 2*np.dot(V, dV[p,:])
190  for q in self.pgrad:
191  Answer['H'][p,q] = 2*np.dot(dV[p,:], dV[q,:])
192  if not in_fd():
193  self.objective = Answer['X']
194  self.FF.make(mvals)
195  return Answer
Subclass of Target for fitting MM optimized geometries to QM optimized geometries.
Nifty functions, intended to be imported by any module within ForceBalance.
def get(self, mvals, AGrad=False, AHess=False)
engine
Option for how much data to write to disk.
Optimization algorithms.
def read_reference_data(self)
Read the reference ab initio data from a file such as qdata.txt.
def __init__(self, options, tgt_opts, forcefield)
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.
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
qfnm
The qdata.txt file that contains the QM energies and forces.
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
mol
Read in the coordinate files and get topology information from PDB.
def Counter()
Definition: optimizer.py:35