ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
quantity.py
Go to the documentation of this file.
1 from __future__ import division
2 from builtins import object
3 import os
4 import numpy as np
5 
6 from forcebalance.finite_difference import fdwrap, f12d3p
7 from forcebalance.molecule import Molecule
8 from forcebalance.nifty import col, flat, statisticalInefficiency
9 from forcebalance.nifty import printcool
10 
11 from collections import OrderedDict
12 
13 from forcebalance.output import getLogger
14 logger = getLogger(__name__)
15 
16 # method mean_stderr
17 def mean_stderr(ts):
18  """Return mean and standard deviation of a time series ts."""
19  return np.mean(ts), \
20  np.std(ts)*np.sqrt(statisticalInefficiency(ts, warn=False)/len(ts))
21 
22 # method energy_derivatives
23 def energy_derivatives(engine, FF, mvals, h, pgrad, length, AGrad=True):
24  """Compute the first derivatives of a set of snapshot energies with respect
25  to the force field parameters. The function calls the finite
26  difference subroutine on the energy_driver subroutine also in this
27  script.
28 
29  Parameters
30  ----------
31  engine : Engine
32  Use this Engine (`GMX`,`TINKER`,`OPENMM` etc.) object to get the energy
33  snapshots.
34  FF : FF
35  Force field object.
36  mvals : list
37  Mathematical parameter values.
38  h : float
39  Finite difference step size.
40  length : int
41  Number of snapshots (length of energy trajectory).
42  AGrad : Boolean
43  Switch that turns derivatives on or off; if off, return all zeros.
44 
45  Returns
46  -------
47  G : np.array
48  Derivative of the energy in a FF.np x length array.
49 
50  """
51  G = np.zeros((FF.np, length))
52 
53  if not AGrad:
54  return G
55  def energy_driver(mvals_):
56  FF.make(mvals_)
57  return engine.energy()
58 
59  ED0 = energy_driver(mvals)
60 
61  for i in pgrad:
62  logger.info("%i %s\r" % (i, (FF.plist[i] + " "*30)))
63  EDG, _ = f12d3p(fdwrap(energy_driver, mvals, i), h, f0=ED0)
64 
65  G[i,:] = EDG[:]
66  return G
67 
68 class Quantity(object):
69  """
70  Base class for thermodynamical quantity used for fitting. This can
71  be any experimental data that can be calculated as an ensemble
72  average from a simulation.
73 
74  Data attributes
75  ---------------
76  name : string
77  Identifier for the quantity that is specified in `quantities` in Target
78  options.
79  engname : string
80  Use this engine to extract the quantity from the simulation results.
81  At present, only `gromacs` is supported.
82  temperature : float
83  Calculate the quantity at this temperature (in K).
84  pressure : float
85  Calculate the quantity at this pressure (in bar).
86 
87  """
88  def __init__(self, engname, temperature, pressure, name=None):
89  self.name = name if name is not None else "empty"
90  self.engname = engname
91  self.temperature = temperature
92  self.pressure = pressure
93 
94  def __str__(self):
95  return "quantity is " + self.name.capitalize() + "."
96 
97  def extract(self, engines, FF, mvals, h, AGrad=True):
98  """Calculate and extract the quantity from MD results. How this is done
99  depends on the quantity and the engine so this must be
100  implemented in the subclass.
101 
102  Parameters
103  ----------
104  engines : list
105  A list of Engine objects that are requred to calculate the quantity.
106  FF : FF
107  Force field object.
108  mvals : list
109  Mathematical parameter values.
110  h : float
111  Finite difference step size.
112  AGrad : Boolean
113  Switch that turns derivatives on or off; if off, return all zeros.
114 
115  Returns
116  -------
117  result : (float, float, np.array)
118  The returned tuple is (Q, Qerr, Qgrad), where Q is the calculated
119  quantity, Qerr is the calculated standard deviation of the quantity,
120  and Qgrad is a M-array with the calculated gradients for the
121  quantity, with M being the number of force field parameters that are
122  being fitted.
123 
124  """
125  logger.error("Extract method not implemented in base class.\n")
126  raise NotImplementedError
127 
128 # class Quantity_Density
130  def __init__(self, engname, temperature, pressure, name=None):
131  """ Density. """
132  super(Quantity_Density, self).__init__(engname, temperature, pressure, name)
133 
134  self.name = name if name is not None else "density"
135 
136  def extract(self, engines, FF, mvals, h, pgrad, AGrad=True):
137  #==========================================#
138  # Physical constants and local variables. #
139  #==========================================#
140  # Energies in kJ/mol and lengths in nanometers.
141  kB = 0.008314472471220214
142  kT = kB*self.temperature
143  Beta = 1.0/kT
144  mBeta = -Beta
145 
146  #======================================================#
147  # Get simulation properties depending on the engines. #
148  #======================================================#
149  if self.engname == "gromacs":
150  # Default name
151  deffnm = os.path.basename(os.path.splitext(engines[0].mdene)[0])
152  # What energy terms are there and what is their order
153  energyterms = engines[0].energy_termnames(edrfile="%s.%s" % (deffnm, "edr"))
154  # Grab energy terms to print and keep track of energy term order.
155  ekeep = ['Total-Energy', 'Potential', 'Kinetic-En.', 'Temperature']
156  ekeep += ['Volume', 'Density']
157 
158  ekeep_order = [key for (key, value) in
159  sorted(energyterms.items(), key=lambda k_v : k_v[1])
160  if key in ekeep]
161 
162  # Perform energy component analysis and return properties.
163  engines[0].callgmx(("g_energy " +
164  "-f %s.%s " % (deffnm, "edr") +
165  "-o %s-energy.xvg " % deffnm +
166  "-xvg no"),
167  stdin="\n".join(ekeep))
168 
169  # Read data and store properties by grabbing columns in right order.
170  data = np.loadtxt("%s-energy.xvg" % deffnm)
171  Energy = data[:, ekeep_order.index("Total-Energy") + 1]
172  Potential = data[:, ekeep_order.index("Potential") + 1]
173  Kinetic = data[:, ekeep_order.index("Kinetic-En.") + 1]
174  Volume = data[:, ekeep_order.index("Volume") + 1]
175  Temperature = data[:, ekeep_order.index("Temperature") + 1]
176  Density = data[:, ekeep_order.index("Density") + 1]
177 
178  #============================================#
179  # Compute the potential energy derivatives. #
180  #============================================#
181  logger.info(("Calculating potential energy derivatives " +
182  "with finite difference step size: %f\n" % h))
183  printcool("Initializing array to length %i" % len(Energy),
184  color=4, bold=True)
185  G = energy_derivatives(engines[0], FF, mvals, h, pgrad, len(Energy), AGrad)
186 
187  #=======================================#
188  # Quantity properties and derivatives. #
189  #=======================================#
190  # Average and error.
191  Rho_avg, Rho_err = mean_stderr(Density)
192  # Analytic first derivative.
193  Rho_grad = mBeta * (flat(np.dot(G, col(Density))) / len(Density) \
194  - np.mean(Density) * np.mean(G, axis=1))
195 
196  return Rho_avg, Rho_err, Rho_grad
197 
198 # class Quantity_H_vap
199 class Quantity_H_vap(Quantity):
200  def __init__(self, engname, temperature, pressure, name=None):
201  """ Enthalpy of vaporization. """
202  super(Quantity_H_vap, self).__init__(engname, temperature, pressure, name)
203 
204  self.name = name if name is not None else "H_vap"
205 
206  def extract(self, engines, FF, mvals, h, pgrad, AGrad=True):
207  #==========================================#
208  # Physical constants and local variables. #
209  #==========================================#
210  # Energies in kJ/mol and lengths in nanometers.
211  kB = 0.008314472471220214
212  kT = kB*self.temperature
213  Beta = 1.0/kT
214  mBeta = -Beta
215  # Conversion factor between 1 kJ/mol -> bar nm^3
216  pconv = 16.6054
217 
218  # Number of molecules in the liquid phase.
219  mol = Molecule(os.path.basename(os.path.splitext(engines[0].mdtraj)[0]) +
220  ".gro")
221  nmol = len(mol.molecules)
222 
223  #======================================================#
224  # Get simulation properties depending on the engines. #
225  #======================================================#
226  if self.engname == "gromacs":
227  # Default names
228  deffnm1 = os.path.basename(os.path.splitext(engines[0].mdene)[0])
229  deffnm2 = os.path.basename(os.path.splitext(engines[1].mdene)[0])
230  # Figure out which energy terms and present and their order.
231  energyterms1 = engines[0].energy_termnames(edrfile="%s.%s" % (deffnm1, "edr"))
232  energyterms2 = engines[1].energy_termnames(edrfile="%s.%s" % (deffnm2, "edr"))
233  # Grab energy terms to print and keep track of energy term order.
234  ekeep1 = ['Total-Energy', 'Potential', 'Kinetic-En.', 'Temperature', 'Volume']
235  ekeep2 = ['Total-Energy', 'Potential', 'Kinetic-En.', 'Temperature']
236 
237  ekeep_order1 = [key for (key, value)
238  in sorted(energyterms1.items(), key=lambda k_v1 : k_v1[1])
239  if key in ekeep1]
240  ekeep_order2 = [key for (key, value)
241  in sorted(energyterms2.items(), key=lambda k_v2 : k_v2[1])
242  if key in ekeep2]
243 
244  # Perform energy component analysis and return properties.
245  engines[0].callgmx(("g_energy " +
246  "-f %s.%s " % (deffnm1, "edr") +
247  "-o %s-energy.xvg " % deffnm1 +
248  "-xvg no"),
249  stdin="\n".join(ekeep1))
250  engines[1].callgmx(("g_energy " +
251  "-f %s.%s " % (deffnm2, "edr") +
252  "-o %s-energy.xvg " % deffnm2 +
253  "-xvg no"),
254  stdin="\n".join(ekeep2))
255 
256  # Read data and store properties by grabbing columns in right order.
257  data1 = np.loadtxt("%s-energy.xvg" % deffnm1)
258  data2 = np.loadtxt("%s-energy.xvg" % deffnm2)
259  Energy = data1[:, ekeep_order1.index("Total-Energy") + 1]
260  Potential = data1[:, ekeep_order1.index("Potential") + 1]
261  Kinetic = data1[:, ekeep_order1.index("Kinetic-En.") + 1]
262  Temperature = data1[:, ekeep_order1.index("Temperature") + 1]
263  Volume = data1[:, ekeep_order1.index("Volume") + 1]
264  mEnergy = data2[:, ekeep_order2.index("Total-Energy") + 1]
265  mPotential = data2[:, ekeep_order2.index("Potential") + 1]
266  mKinetic = data2[:, ekeep_order2.index("Kinetic-En.") + 1]
267 
268  #============================================#
269  # Compute the potential energy derivatives. #
270  #============================================#
271  logger.info(("Calculating potential energy derivatives " +
272  "with finite difference step size: %f\n" % h))
273  printcool("Initializing arrays to lengths %d" % len(Energy),
274  color=4, bold=True)
275 
276  G = energy_derivatives(engines[0], FF, mvals, h, pgrad, len(Energy), AGrad)
277  Gm = energy_derivatives(engines[1], FF, mvals, h, pgrad, len(mEnergy), AGrad)
278 
279  #=======================================#
280  # Quantity properties and derivatives. #
281  #=======================================#
282  # Average and error.
283  E_avg, E_err = mean_stderr(Energy)
284  Em_avg, Em_err = mean_stderr(mEnergy)
285  Vol_avg, Vol_err = mean_stderr(Volume)
286 
287  Hvap_avg = Em_avg - E_avg/nmol - self.pressure*Vol_avg/nmol/pconv + kT
288  Hvap_err = np.sqrt((E_err/nmol)**2 + Em_err**2
289  + (self.pressure**2) * (Vol_err**2)/(float(nmol)**2)/(pconv**2))
290  # Analytic first derivative.
291  Hvap_grad = np.mean(Gm, axis=1)
292  Hvap_grad += mBeta * (flat(np.dot(Gm, col(mEnergy))) / len(mEnergy) \
293  - np.mean(mEnergy) * np.mean(Gm, axis=1))
294  Hvap_grad -= np.mean(G, axis=1)/nmol
295  Hvap_grad += Beta * (flat(np.dot(G, col(Energy))) / len(Energy) \
296  - np.mean(Energy) * np.mean(G, axis=1))/nmol
297  Hvap_grad += (Beta*self.pressure/nmol/pconv) * \
298  (flat(np.dot(G, col(Volume))) / len(Volume) \
299  - np.mean(Volume) * np.mean(G, axis=1))
300 
301  return Hvap_avg, Hvap_err, Hvap_grad
302 
Nifty functions, intended to be imported by any module within ForceBalance.
def flat(vec)
Given any list, array, or matrix, return a single-index array.
Definition: nifty.py:467
def extract(self, engines, FF, mvals, h, pgrad, AGrad=True)
Definition: quantity.py:141
def __init__(self, engname, temperature, pressure, name=None)
Density.
Definition: quantity.py:136
def statisticalInefficiency(A_n, B_n=None, fast=False, mintime=3, warn=True)
Compute the (cross) statistical inefficiency of (two) timeseries.
Definition: nifty.py:740
def mean_stderr(ts)
Return mean and standard deviation of a time series ts.
Definition: quantity.py:19
def fdwrap(func, mvals0, pidx, key=None, kwargs)
A function wrapper for finite difference designed for differentiating 'get'-type functions.
def col(vec)
Given any list, array, or matrix, return a 1-column 2D array.
Definition: nifty.py:448
def __init__(self, engname, temperature, pressure, name=None)
Definition: quantity.py:91
def extract(self, engines, FF, mvals, h, AGrad=True)
Calculate and extract the quantity from MD results.
Definition: quantity.py:128
def printcool(text, sym="#", bold=False, color=2, ansi=None, bottom='-', minwidth=50, center=True, sym2="=")
Cool-looking printout for slick formatting of output.
Definition: nifty.py:321
def __str__(self)
Definition: quantity.py:97
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
Base class for thermodynamical quantity used for fitting.
Definition: quantity.py:90
def extract(self, engines, FF, mvals, h, pgrad, AGrad=True)
Definition: quantity.py:212
def energy_derivatives(engine, FF, mvals, h, pgrad, length, AGrad=True)
Compute the first derivatives of a set of snapshot energies with respect to the force field parameter...
Definition: quantity.py:52