ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
liquid.py
Go to the documentation of this file.
1 """ @package forcebalance.liquid Matching of liquid bulk properties. Under development.
2 
3 author Lee-Ping Wang
4 @date 04/2012
5 """
6 from __future__ import division
7 from __future__ import print_function
8 
9 from builtins import str
10 from builtins import zip
11 from builtins import range
12 import abc
13 import os
14 import shutil
16 from forcebalance.nifty import *
17 from forcebalance.nifty import _exec
18 from forcebalance.target import Target
19 import numpy as np
20 from forcebalance.molecule import Molecule
21 from re import match, sub
22 import subprocess
23 from subprocess import PIPE
24 try:
25  from lxml import etree
26 except: pass
27 from pymbar import pymbar
28 import itertools
29 from forcebalance.optimizer import Counter
30 from collections import defaultdict, namedtuple, OrderedDict
31 import csv
32 import copy
33 
34 from forcebalance.output import getLogger
35 logger = getLogger(__name__)
36 
37 def weight_info(W, PT, N_k, verbose=True, PTS=None):
38  C = []
39  N = 0
40  W += 1.0e-300
41  I = np.exp(-1*np.sum((W*np.log(W))))
42  for ns in N_k:
43  C.append(sum(W[N:N+ns]))
44  N += ns
45  C = np.array(C)
46  if PTS is not None:
47  if len(PTS) != len(N_k):
48  logger.error("PTS array (phase point labels) must equal length of N_k array (# of trajectories)\n")
49  raise RuntimeError
50  fs = max([len(i) for i in PTS])
51  else:
52  fs = 6
53  if verbose:
54  logger.info("MBAR Results for Phase Point %s, Contributions:\n" % str(PT))
55  line1 = ""
56  line2 = ""
57  tfl = 0
58  # If we have phase point labels, then presumably we can have less cluttered printout
59  # by printing only the lines with the largest contribution
60  pl = 0 if PTS is not None else 1
61  for i, Ci in enumerate(C):
62  if PTS is not None:
63  line1 += "%%%is " % fs % PTS[i]
64  if Ci == np.max(C):
65  line2 += "\x1b[91m%%%i.1f%%%%\x1b[0m " % (fs-1) % (Ci*100)
66  pl = 1
67  else:
68  line2 += "%%%i.1f%%%% " % (fs-1) % (Ci*100)
69  tfl += (fs+1)
70  if tfl >= 80:
71  if len(line1) > 0:
72  if pl: logger.info(line1+"\n")
73  if pl: logger.info(line2+"\n")
74  line1 = ""
75  line2 = ""
76  tfl = 0
77  pl = 0 if PTS is not None else 1
78  if tfl > 0 and pl:
79  if len(line1) > 0:
80  logger.info(line1+"\n")
81  logger.info(line2+"\n")
82  logger.info("\n")
83  logger.info("InfoContent: % .2f snapshots (%.2f %%)\n" % (I, 100*I/len(W)))
84  return C
85 
86 # NPT_Trajectory = namedtuple('NPT_Trajectory', ['fnm', 'Rhos', 'pVs', 'Energies', 'Grads', 'mEnergies', 'mGrads', 'Rho_errs', 'Hvap_errs'])
87 
88 class Liquid(Target):
89 
90  """ Subclass of Target for liquid property matching."""
91 
92  def __init__(self,options,tgt_opts,forcefield):
93  # Initialize base class
94  super(Liquid,self).__init__(options,tgt_opts,forcefield)
95  # Weight of the density
96  self.set_option(tgt_opts,'w_rho',forceprint=True)
97  # Weight of the enthalpy of vaporization
98  self.set_option(tgt_opts,'w_hvap',forceprint=True)
99  # Weight of the thermal expansion coefficient
100  self.set_option(tgt_opts,'w_alpha',forceprint=True)
101  # Weight of the isothermal compressibility
102  self.set_option(tgt_opts,'w_kappa',forceprint=True)
103  # Weight of the isobaric heat capacity
104  self.set_option(tgt_opts,'w_cp',forceprint=True)
105  # Weight of the dielectric constant
106  self.set_option(tgt_opts,'w_eps0',forceprint=True)
107  # Normalize the contributions to the objective function
108  self.set_option(tgt_opts,'w_normalize',forceprint=True)
109  # Optionally pause on the zeroth step
110  self.set_option(tgt_opts,'manual')
111  # Don't target the average enthalpy of vaporization and allow it to freely float (experimental)
112  self.set_option(tgt_opts,'hvap_subaverage')
113  # Number of time steps in the liquid "equilibration" run
114  self.set_option(tgt_opts,'liquid_eq_steps',forceprint=True)
115  # Number of time steps in the liquid "production" run
116  self.set_option(tgt_opts,'liquid_md_steps',forceprint=True)
117  # Number of time steps in the gas "equilibration" run
118  self.set_option(tgt_opts,'gas_eq_steps',forceprint=True)
119  # Number of time steps in the gas "production" run
120  self.set_option(tgt_opts,'gas_md_steps',forceprint=True)
121  # Cutoff for nonbonded interactions in the liquid
122  if tgt_opts['nonbonded_cutoff'] is not None:
123  self.set_option(tgt_opts,'nonbonded_cutoff')
124  # Cutoff for vdW interactions if different from other nonbonded interactions
125  if tgt_opts['vdw_cutoff'] is not None:
126  self.set_option(tgt_opts,'vdw_cutoff')
127  # Time step length (in fs) for the liquid production run
128  self.set_option(tgt_opts,'liquid_timestep',forceprint=True)
129  # Time interval (in ps) for writing coordinates
130  self.set_option(tgt_opts,'liquid_interval',forceprint=True)
131  # Time step length (in fs) for the gas production run
132  self.set_option(tgt_opts,'gas_timestep',forceprint=True)
133  # Time interval (in ps) for writing coordinates
134  self.set_option(tgt_opts,'gas_interval',forceprint=True)
135  # Adjust simulation length in response to simulation uncertainty
136  self.set_option(tgt_opts,'adapt_errors',forceprint=True)
137  # Minimize the energy prior to running any dynamics
138  self.set_option(tgt_opts,'minimize_energy',forceprint=True)
139  # Isolated dipole (debye) for analytic self-polarization correction.
140  self.set_option(tgt_opts,'self_pol_mu0',forceprint=True)
141  # Molecular polarizability (ang**3) for analytic self-polarization correction.
142  self.set_option(tgt_opts,'self_pol_alpha',forceprint=True)
143  # Set up the simulation object for self-polarization correction.
144  self.do_self_pol = (self.self_pol_mu0 > 0.0 and self.self_pol_alpha > 0.0)
145  # Enable anisotropic periodic box
146  self.set_option(tgt_opts,'anisotropic_box',forceprint=True)
147  # Whether to save trajectories (0 = never, 1 = delete after good step, 2 = keep all)
148  self.set_option(tgt_opts,'save_traj')
149  # Set the number of molecules by hand (in case ForceBalance doesn't get the right number from the structure)
150  self.set_option(tgt_opts,'n_molecules')
151  # Weight of surface tension
152  self.set_option(tgt_opts,'w_surf_ten',forceprint=True)
153  # Number of time steps in surface tension NVT "equilibration" run
154  self.set_option(tgt_opts,'nvt_eq_steps', forceprint=True)
155  # Number of time steps in surface tension NVT "production" run
156  self.set_option(tgt_opts,'nvt_md_steps', forceprint=True)
157  # Time step length (in fs) for the NVT production run
158  self.set_option(tgt_opts,'nvt_timestep', forceprint=True)
159  # Time interval (in ps) for writing coordinates
160  self.set_option(tgt_opts,'nvt_interval', forceprint=True)
161  # Switch for pure numerical gradients
162  self.set_option(tgt_opts,'pure_num_grad', forceprint=True)
163  # Finite difference size for pure_num_grad
164  self.set_option(tgt_opts,'liquid_fdiff_h', forceprint=True)
165  #======================================#
166  # Variables which are set here #
167  #======================================#
168 
170  self.loop_over_snapshots = False
171  # Read in liquid starting coordinates.
172  if not os.path.exists(os.path.join(self.root, self.tgtdir, self.liquid_coords)):
173  logger.error("%s doesn't exist; please provide liquid_coords option\n" % self.liquid_coords)
174  raise RuntimeError
175  self.liquid_mol = Molecule(os.path.join(self.root, self.tgtdir, self.liquid_coords), toppbc=True)
176 
177  # Manully set n_molecules if needed
178  if self.n_molecules >= 0:
179  if self.n_molecules == len(self.liquid_mol.molecules):
180  logger.info("User-provided number of molecules matches auto-detected value (%i)\n" % self.n_molecules)
181  else:
182  logger.info("User-provided number of molecules (%i) overrides auto-detected value (%i)\n" % (self.n_molecules, len(self.liquid_mol.molecules)))
183  else:
184  self.n_molecules = len(self.liquid_mol.molecules)
185  if len(set([len(m) for m in self.liquid_mol.molecules])) != 1:
186  warn_press_key("Possible issue because molecules are not all the same size! Sizes detected: %s" % str(set([len(m) for m in self.liquid_mol.molecules])), timeout=30)
187  else:
188  logger.info("Autodetected %i molecules with %i atoms each in liquid coordinates\n" % (self.n_molecules, len(self.liquid_mol.molecules[0])))
189 
190  # Read in gas starting coordinates.
191  if not os.path.exists(os.path.join(self.root, self.tgtdir, self.gas_coords)):
192  logger.error("%s doesn't exist; please provide gas_coords option\n" % self.gas_coords)
193  raise RuntimeError
194  self.gas_mol = Molecule(os.path.join(self.root, self.tgtdir, self.gas_coords))
195  # List of trajectory files that may be deleted if self.save_traj == 1.
196  self.last_traj = []
197  # Extra files to be copied back at the end of a run.
198  self.extra_output = []
199 
200  self.read_data()
201  # Extra files to be linked into the temp-directory.
202  self.nptfiles += [self.liquid_coords, self.gas_coords]
203  # Scripts to be copied from the ForceBalance installation directory.
204  self.scripts += ['npt.py']
205  # NVT simulation parameters for computing Surface Tension
206  if 'surf_ten' in self.RefData:
207  # Check if nvt_coords exist
208  if not os.path.exists(os.path.join(self.root, self.tgtdir, self.nvt_coords)):
209  logger.error("Surface tension calculation requires %s, but it is not found."%self.nvt_coords)
210  raise RuntimeError
211  self.surf_ten_mol = Molecule(os.path.join(self.root, self.tgtdir, self.nvt_coords), toppbc=True)
212  # Extra files to be linked into the temp-directory.
213  self.nvtfiles += [self.nvt_coords]
214  self.scripts += ['nvt.py']
215  # Don't read objective.p when calling meta_get()
216  # self.read_objective = False
217 
218  #======================================#
219  # UNDER DEVELOPMENT #
220  #======================================#
221  # Put stuff here that I'm not sure about. :)
222  np.set_printoptions(precision=4, linewidth=100)
223  np.seterr(under='ignore')
224 
225  self.SavedTraj = defaultdict(dict)
226 
227  self.MBarEnergy = defaultdict(lambda:defaultdict(dict))
228 
230  self.AllResults = defaultdict(lambda:defaultdict(list))
231 
232  def post_init(self, options):
233  # Prepare the temporary directory.
235  # Build keyword dictionary to pass to engine.
236  if self.do_self_pol:
237  self.gas_engine_args.update(self.OptionDict)
238  self.gas_engine_args.update(options)
239  del self.gas_engine_args['name']
240  # Create engine object for gas molecule to do the polarization correction.
241  self.gas_engine = self.engine_(target=self, mol=self.gas_mol, name="selfpol", **self.gas_engine_args)
242  # Don't read indicate.log when calling meta_indicate()
243  self.read_indicate = False
244  self.write_indicate = False
247  """ Prepare the temporary directory by copying in important files. """
248  abstempdir = os.path.join(self.root,self.tempdir)
249  for f in self.nptfiles + (self.nvtfiles if hasattr(self, 'nvtfiles') else []):
250  LinkFile(os.path.join(self.root, self.tgtdir, f), os.path.join(abstempdir, f))
251  for f in self.scripts:
252  LinkFile(os.path.join(os.path.split(__file__)[0],"data",f),os.path.join(abstempdir,f))
253 
254  def read_data(self):
255  # Read the 'data.csv' file. The file should contain guidelines.
256  with open(os.path.join(self.tgtdir,'data.csv'),'rU') as f: R0 = list(csv.reader(f))
257  # All comments are erased.
258  R1 = [[sub('#.*$','',word) for word in line] for line in R0 if len(line[0]) > 0 and line[0][0] != "#"]
259  # All empty lines are deleted and words are converted to lowercase.
260  R = [[wrd.lower() for wrd in line] for line in R1 if any([len(wrd) for wrd in line]) > 0]
261  global_opts = OrderedDict()
262  found_headings = False
263  known_vars = ['mbar','rho','hvap','alpha','kappa','cp','eps0','cvib_intra',
264  'cvib_inter','cni','devib_intra','devib_inter', 'surf_ten']
265  self.RefData = OrderedDict()
266  for line in R:
267  if line[0] == "global":
268  # Global options are mainly denominators for the different observables.
269  if isfloat(line[2]):
270  global_opts[line[1]] = float(line[2])
271  elif line[2].lower() == 'false':
272  global_opts[line[1]] = False
273  elif line[2].lower() == 'true':
274  global_opts[line[1]] = True
275  elif not found_headings:
276  found_headings = True
277  headings = line
278  if len(set(headings)) != len(headings):
279  logger.error('Column headings in data.csv must be unique\n')
280  raise RuntimeError
281  if 'p' not in headings:
282  logger.error('There must be a pressure column heading labeled by "p" in data.csv\n')
283  raise RuntimeError
284  if 't' not in headings:
285  logger.error('There must be a temperature column heading labeled by "t" in data.csv\n')
286  raise RuntimeError
287  elif found_headings:
288  try:
289  # Temperatures are in kelvin.
290  t = [float(val) for head, val in zip(headings,line) if head == 't'][0]
291  # For convenience, users may input the pressure in atmosphere or bar.
292  pval = [float(val.split()[0]) for head, val in zip(headings,line) if head == 'p'][0]
293  punit = [val.split()[1] if len(val.split()) >= 1 else "atm" for head, val in zip(headings,line) if head == 'p'][0]
294  unrec = set([punit]).difference(['atm','bar'])
295  if len(unrec) > 0:
296  logger.error('The pressure unit %s is not recognized, please use bar or atm\n' % unrec[0])
297  raise RuntimeError
298  # This line actually reads the reference data and inserts it into the RefData dictionary of dictionaries.
299  for head, val in zip(headings,line):
300  if head == 't' or head == 'p' : continue
301  if isfloat(val):
302  self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = float(val)
303  elif val.lower() == 'true':
304  self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = True
305  elif val.lower() == 'false':
306  self.RefData.setdefault(head,OrderedDict([]))[(t,pval,punit)] = False
307  except:
308  logger.error(line + '\n')
309  logger.error('Encountered an error reading this line!\n')
310  raise RuntimeError
311  else:
312  logger.error(line + '\n')
313  logger.error('I did not recognize this line!\n')
314  raise RuntimeError
315  # Check the reference data table for validity.
316  default_denoms = defaultdict(int)
317  PhasePoints = set()
318  RefData_copy = copy.deepcopy(self.RefData)
319  for head in self.RefData:
320  if head not in known_vars+[i+"_wt" for i in known_vars]:
321  # Only hard-coded properties may be recognized.
322  logger.error("The column heading %s is not recognized in data.csv\n" % head)
323  raise RuntimeError
324  if head in known_vars:
325  if head+"_wt" not in self.RefData:
326  # If the phase-point weights are not specified in the reference data file, initialize them all to one.
327  RefData_copy[head+"_wt"] = OrderedDict([(key, 1.0) for key in self.RefData[head]])
328  wts = np.array(list(RefData_copy[head+"_wt"].values()))
329  dat = np.array(list(self.RefData[head].values()))
330  avg = np.average(dat, weights=wts)
331  if len(wts) > 1:
332  # If there is more than one data point, then the default denominator is the
333  # standard deviation of the experimental values.
334  default_denoms[head+"_denom"] = np.sqrt(np.dot(wts, (dat-avg)**2)/wts.sum())
335  else:
336  # If there is only one data point, then the denominator is just the single
337  # data point itself.
338  default_denoms[head+"_denom"] = np.sqrt(np.abs(dat[0]))
339  PhasePoints |= set(self.RefData[head].keys())
340  # This prints out all of the reference data.
341  # printcool_dictionary(self.RefData[head],head)
342  self.RefData = RefData_copy
343  # Here we sort all available PhasePoints by pressure then temperature
344  self.PhasePoints = sorted(list(PhasePoints), key=lambda x: (x[1], x[0]))
345  # Create labels for the directories.
346  self.Labels = ["%.2fK-%.1f%s" % i for i in self.PhasePoints]
347  logger.debug("global_opts:\n%s\n" % str(global_opts))
348  logger.debug("default_denoms:\n%s\n" % str(default_denoms))
349  for opt in global_opts:
350  if "_denom" in opt:
351  # Record entries from the global_opts dictionary so they can be retrieved from other methods.
352  self.set_option(global_opts,opt,default=default_denoms[opt])
353  else:
354  self.set_option(global_opts,opt)
355 
356  def check_files(self, there):
357  there = os.path.abspath(there)
358  havepts = 0
359  if all([i in os.listdir(there) for i in self.Labels]):
360  for d in os.listdir(there):
361  if d in self.Labels:
362  if os.path.exists(os.path.join(there, d, 'npt_result.p')):
363  havepts += 1
364  if (float(havepts)/len(self.Labels)) > 0.75:
365  return 1
366  else:
367  return 0
368 
369  def npt_simulation(self, temperature, pressure, simnum):
370  """ Submit a NPT simulation to the Work Queue. """
371  wq = getWorkQueue()
372  if not os.path.exists('npt_result.p'):
373  link_dir_contents(os.path.join(self.root,self.rundir),os.getcwd())
374  self.last_traj += [os.path.join(os.getcwd(), i) for i in self.extra_output]
375  self.liquid_mol[simnum%len(self.liquid_mol)].write(self.liquid_coords, ftype='tinker' if self.engname == 'tinker' else None)
376  cmdstr = '%s python npt.py %s %.3f %.3f' % (self.nptpfx, self.engname, temperature, pressure)
377  if wq is None:
378  logger.info("Running condensed phase simulation locally.\n")
379  logger.info("You may tail -f %s/npt.out in another terminal window\n" % os.getcwd())
380  _exec(cmdstr, copy_stderr=True, outfnm='npt.out')
381  else:
382  if hasattr(self, 'mol2'):
383  if hasattr(self, 'FF'):
384  mol2_send = list(set(self.mol2).difference(set(self.FF.fnms)))
385  else:
386  mol2_send = self.mol2
387  else:
388  mol2_send = []
389  queue_up(wq, command = cmdstr+' > npt.out 2>&1 ',
390  input_files = self.nptfiles + self.scripts + mol2_send + ['forcebalance.p'],
391  output_files = ['npt_result.p', 'npt.out'] + self.extra_output, tgt=self)
392 
393  def nvt_simulation(self, temperature):
394  """ Submit a NVT simulation to the Work Queue. """
395  wq = getWorkQueue()
396  if not os.path.exists('nvt_result.p'):
397  link_dir_contents(os.path.join(self.root,self.rundir),os.getcwd())
398  cmdstr = '%s python nvt.py %s %.3f' % (self.nptpfx, self.engname, temperature)
399  if wq is None:
400  logger.info("Running condensed phase simulation locally.\n")
401  logger.info("You may tail -f %s/nvt.out in another terminal window\n" % os.getcwd())
402  _exec(cmdstr, copy_stderr=True, outfnm='nvt.out')
403  else:
404  if hasattr(self, 'mol2'):
405  if hasattr(self, 'FF'):
406  mol2_send = list(set(self.mol2).difference(set(self.FF.fnms)))
407  else:
408  mol2_send = self.mol2
409  else:
410  mol2_send = []
411  queue_up(wq, command = cmdstr+' > nvt.out 2>&1 ',
412  input_files = self.nvtfiles + self.scripts + mol2_send + ['forcebalance.p'],
413  output_files = ['nvt_result.p', 'nvt.out'] + self.extra_output, tgt=self)
414 
415  def polarization_correction(self,mvals):
416  self.FF.make(mvals)
417  # print mvals
418  ddict = self.gas_engine.multipole_moments(optimize=True)['dipole']
419  d = np.array(list(ddict.values()))
420  if not in_fd():
421  logger.info("The molecular dipole moment is % .3f debye\n" % np.linalg.norm(d))
422  # Taken from the original OpenMM interface code, this is how we calculate the conversion factor.
423  # dd2 = ((np.linalg.norm(d)-self.self_pol_mu0)*debye)**2
424  # eps0 = 8.854187817620e-12 * coulomb**2 / newton / meter**2
425  # epol = 0.5*dd2/(self.self_pol_alpha*angstrom**3*4*np.pi*eps0)/(kilojoule_per_mole/AVOGADRO_CONSTANT_NA)
426  # In [2]: eps0 = 8.854187817620e-12 * coulomb**2 / newton / meter**2
427  # In [7]: 1.0 * debye ** 2 / (1.0 * angstrom**3*4*np.pi*eps0) / (kilojoule_per_mole/AVOGADRO_CONSTANT_NA)
428  # Out[7]: 60.240179789402056
429  convert = 60.240179789402056
430  dd2 = (np.linalg.norm(d)-self.self_pol_mu0)**2
431  epol = 0.5*convert*dd2/self.self_pol_alpha
432  return epol
433 
434  def indicate(self):
435  AGrad = hasattr(self, 'Gp')
436  PrintDict = OrderedDict()
437  def print_item(key, heading, physunit):
438  if self.Xp[key] > 0:
439  printcool_dictionary(self.Pp[key], title='%s %s%s\nTemperature Pressure Reference Calculated +- Stdev Delta Weight Term ' %
440  (self.name, heading, " (%s) " % physunit if physunit else ""), bold=True, color=4, keywidth=15)
441  bar = printcool("%s objective function: % .3f%s" % (heading, self.Xp[key], ", Derivative:" if AGrad else ""))
442  if AGrad:
443  self.FF.print_map(vals=self.Gp[key])
444  logger.info(bar)
445  PrintDict[heading] = "% 10.5f % 8.3f % 14.5e" % (self.Xp[key], self.Wp[key], self.Xp[key]*self.Wp[key])
446 
447  print_item("Rho", "Density", "kg m^-3")
448  print_item("Hvap", "Enthalpy of Vaporization", "kJ mol^-1")
449  print_item("Alpha", "Thermal Expansion Coefficient", "10^-4 K^-1")
450  print_item("Kappa", "Isothermal Compressibility", "10^-6 bar^-1")
451  print_item("Cp", "Isobaric Heat Capacity", "cal mol^-1 K^-1")
452  print_item("Eps0", "Dielectric Constant", None)
453  print_item("Surf_ten", "Surface Tension", "mN m^-1")
454 
455  PrintDict['Total'] = "% 10s % 8s % 14.5e" % ("","",self.Objective)
456 
457  Title = "%s Condensed Phase Properties:\n %-20s %40s" % (self.name, "Property Name", "Residual x Weight = Contribution")
458  printcool_dictionary(PrintDict,color=4,title=Title,keywidth=31)
459  return
460 
461  def objective_term(self, points, expname, calc, err, grad, name="Quantity", SubAverage=False):
462  if expname in self.RefData:
463  exp = self.RefData[expname]
464  Weights = self.RefData[expname+"_wt"]
465  Denom = getattr(self,expname+"_denom",1.0)
466  else:
467  # If the reference data doesn't exist then return nothing.
468  return 0.0, np.zeros(self.FF.np), np.zeros((self.FF.np,self.FF.np)), None
469 
470  Sum = sum(Weights.values())
471  for i in Weights:
472  Weights[i] /= Sum
473  logger.info("Weights have been renormalized to " + str(sum(Weights.values())) + "\n")
474  # Use least-squares or hyperbolic (experimental) objective.
475  LeastSquares = True
476 
477  logger.info("Physical quantity %s uses denominator = % .4f\n" % (name, Denom))
478  if not LeastSquares:
479  # If using a hyperbolic functional form
480  # we still want the contribution to the
481  # objective function to be the same when
482  # Delta = Denom.
483  Denom /= 3 ** 0.5
484 
485  Objective = 0.0
486  Gradient = np.zeros(self.FF.np)
487  Hessian = np.zeros((self.FF.np,self.FF.np))
488  Objs = {}
489  GradMap = []
490  avgCalc = 0.0
491  avgExp = 0.0
492  avgGrad = np.zeros(self.FF.np)
493 
494  for PT in points:
495  avgCalc += Weights[PT]*calc[PT]
496  avgExp += Weights[PT]*exp[PT]
497  avgGrad += Weights[PT]*grad[PT]
498 
499  for i, PT in enumerate(points):
500  if SubAverage:
501  G = grad[PT]-avgGrad
502  Delta = calc[PT] - exp[PT] - avgCalc + avgExp
503  else:
504  G = grad[PT]
505  Delta = calc[PT] - exp[PT]
506  if LeastSquares:
507  # Least-squares objective function.
508  ThisObj = Weights[PT] * Delta ** 2 / Denom**2
509  Objs[PT] = ThisObj
510  ThisGrad = 2.0 * Weights[PT] * Delta * G / Denom**2
511  GradMap.append(G)
512  Objective += ThisObj
513  Gradient += ThisGrad
514  # Gauss-Newton approximation to the Hessian.
515  Hessian += 2.0 * Weights[PT] * (np.outer(G, G)) / Denom**2
516  else:
517  # L1-like objective function.
518  D = Denom
519  S = Delta**2 + D**2
520  ThisObj = Weights[PT] * (S**0.5-D) / Denom
521  ThisGrad = Weights[PT] * (Delta/S**0.5) * G / Denom
522  ThisHess = Weights[PT] * (1/S**0.5-Delta**2/S**1.5) * np.outer(G,G) / Denom
523  Objs[PT] = ThisObj
524  GradMap.append(G)
525  Objective += ThisObj
526  Gradient += ThisGrad
527  Hessian += ThisHess
528  GradMapPrint = [["#PhasePoint"] + self.FF.plist]
529  for PT, g in zip(points,GradMap):
530  GradMapPrint.append([' %8.2f %8.1f %3s' % PT] + ["% 9.3e" % i for i in g])
531  o = wopen('gradient_%s.dat' % name)
532  for line in GradMapPrint:
533  print(' '.join(line), file=o)
534  o.close()
535 
536  Delta = np.array([calc[PT] - exp[PT] for PT in points])
537  delt = {PT : r for PT, r in zip(points,Delta)}
538  print_out = OrderedDict([(' %8.2f %8.1f %3s' % PT,"%9.3f %9.3f +- %-7.3f % 7.3f % 9.5f % 9.5f" % (exp[PT],calc[PT],err[PT],delt[PT],Weights[PT],Objs[PT])) for PT in calc])
539  return Objective, Gradient, Hessian, print_out
540 
541  def submit_jobs(self, mvals, AGrad=True, AHess=True):
542  # This routine is called by Objective.stage() will run before "get".
543  # It submits the jobs to the Work Queue and the stage() function will wait for jobs to complete.
544  #
545  # First dump the force field to a pickle file
546  printcool("Target: %s - launching MD simulations\nTime steps: %i (eq) + %i (md)" % (self.name, self.liquid_eq_steps, self.liquid_md_steps), color=0)
547  if 'surf_ten' in self.RefData:
548  logger.info("Launching additional NVT simulations for computing surface tension. Time steps: %i (eq) + %i (md)\n" % (self.nvt_eq_steps, self.nvt_md_steps))
549 
550  if AGrad and self.pure_num_grad:
551  lp_dump((self.FF,mvals,self.OptionDict,False),'forcebalance.p')
552  else:
553  lp_dump((self.FF,mvals,self.OptionDict,AGrad),'forcebalance.p')
554 
555  # Give the user an opportunity to copy over data from a previous (perhaps failed) run.
556  if (not self.evaluated) and self.manual:
557  warn_press_key("Now's our chance to fill the temp directory up with data!", timeout=7200)
558 
559  # If self.save_traj == 1, delete the trajectory files from a previous good optimization step.
560  if self.evaluated and self.goodstep and self.save_traj < 2:
561  for fn in self.last_traj:
562  if os.path.exists(fn):
563  os.remove(fn)
564  self.last_traj = []
565 
566  def submit_one_setm():
567  snum = 0
568  for label, pt in zip(self.Labels, self.PhasePoints):
569  T = pt[0]
570  P = pt[1]
571  Punit = pt[2]
572  if Punit == 'bar':
573  P *= 1.0 / 1.01325
574  if not os.path.exists(label):
575  os.makedirs(label)
576  os.chdir(label)
577  self.npt_simulation(T,P,snum)
578  if 'surf_ten' in self.RefData and pt in self.RefData['surf_ten']:
579  self.nvt_simulation(T)
580  os.chdir('..')
581  snum += 1
582 
583  # Set up and run the simulations.
584  submit_one_setm()
585  # if pure_num_grad is set, submit additional simulations with AGrad=False
586  if AGrad and self.pure_num_grad:
587  logger.info("Running in Pure Numerical Gradient Mode! Two additional simulation will be submitted for each parameter.\n")
588  for i_m in range(len(mvals)):
589  for delta_m in [-self.liquid_fdiff_h, +self.liquid_fdiff_h]:
590  pure_num_grad_label = 'mvals_%03d_%f' % (i_m, delta_m)
591  if not os.path.exists(pure_num_grad_label):
592  os.mkdir(pure_num_grad_label)
593  os.chdir(pure_num_grad_label)
594  # copy the original mvals and perturb
595  new_mvals = copy.copy(mvals)
596  new_mvals[i_m] += delta_m
597  # create a new forcebalance.p, turn off gradient
598  lp_dump((self.FF, new_mvals, self.OptionDict, False),'forcebalance.p')
599  # link files from parent folder to here
600  link_dir_contents(os.path.join(self.root,self.rundir),os.getcwd())
601  # backup self.rundir
602  rundir_backup = self.rundir
603  # change the self.rundir temporarily so the new forcebalance.p will be used by npt_simulation() and nvt_simulation()
604  self.rundir = os.getcwd()
605  # submit simulations
606  submit_one_setm()
607  # change the self.rundir back
608  self.rundir = rundir_backup
609  os.chdir('..')
610 
611 
612  def read(self, mvals, AGrad=True, AHess=True):
613 
614  """
615  Read in time series for all previous iterations.
616  """
617 
618  unpack = lp_load('forcebalance.p')
619  mvals1 = unpack[1]
620  if len(mvals) > 0 and (np.max(np.abs(mvals1 - mvals)) > 1e-3):
621  warn_press_key("mvals from forcebalance.p does not match up with internal values! (Are you reading data from a previous run?)\nmvals(call)=%s mvals(disk)=%s" % (mvals, mvals1))
622 
623  for dn in range(Counter()-1, -1, -1):
624  cwd = os.getcwd()
625  os.chdir(self.absrd(inum=dn))
626  mprev = np.loadtxt('mvals.txt')
627  Results = {}
628  Points = [] # These are the phase points for which data exists.
629  mPoints = [] # These are the phase points to use for enthalpy of vaporization; if we're scanning pressure then set hvap_wt for higher pressures to zero.
630  tt = 0
631  logger.info('Reading liquid data from %s\n' % os.getcwd())
632  for label, PT in zip(self.Labels, self.PhasePoints):
633  if os.path.exists('./%s/npt_result.p' % label):
634  Points.append(PT)
635  Results[tt] = lp_load('./%s/npt_result.p' % label)
636  if 'hvap' in self.RefData and PT[0] not in [i[0] for i in mPoints]:
637  mPoints.append(PT)
638  tt += 1
639  else:
640  logger.warning('In %s :\n' % os.getcwd())
641  logger.warning('The file ./%s/npt_result.p does not exist so we cannot read it\n' % label)
642  pass
643  if len(Points) == 0:
644  logger.error('The liquid simulations have terminated with \x1b[1;91mno readable data\x1b[0m - this is a problem!\n')
645  raise RuntimeError
646 
647  # Assign variable names to all the stuff in npt_result.p
648  Rhos, Vols, Potentials, Energies, Dips, Grads, GDips, mPotentials, mEnergies, mGrads, \
649  Rho_errs, Hvap_errs, Alpha_errs, Kappa_errs, Cp_errs, Eps0_errs, NMols = ([Results[t][i] for t in range(len(Points))] for i in range(17))
650  # Determine the number of molecules
651  if len(set(NMols)) != 1:
652  logger.error(str(NMols))
653  logger.error('The above list should only contain one number - the number of molecules\n')
654  raise RuntimeError
655  else:
656  NMol = list(set(NMols))[0]
657 
658  if not self.adapt_errors:
659  self.AllResults = defaultdict(lambda:defaultdict(list))
660 
661  astrm = astr(mprev)
662  if len(Points) != len(self.Labels):
663  logger.info("Data sets is not full, will not use for concatenation.\n")
664  astrm += "_"*(dn+1)
665 
666  self.AllResults[astrm]['Pts'].append(Points)
667  self.AllResults[astrm]['mPts'].append(mPoints)
668  self.AllResults[astrm]['E'].append(np.array(Energies))
669  self.AllResults[astrm]['V'].append(np.array(Vols))
670  self.AllResults[astrm]['R'].append(np.array(Rhos))
671  self.AllResults[astrm]['Dx'].append(np.array([d[:,0] for d in Dips]))
672  self.AllResults[astrm]['Dy'].append(np.array([d[:,1] for d in Dips]))
673  self.AllResults[astrm]['Dz'].append(np.array([d[:,2] for d in Dips]))
674  self.AllResults[astrm]['G'].append(np.array(Grads))
675  self.AllResults[astrm]['GDx'].append(np.array([gd[0] for gd in GDips]))
676  self.AllResults[astrm]['GDy'].append(np.array([gd[1] for gd in GDips]))
677  self.AllResults[astrm]['GDz'].append(np.array([gd[2] for gd in GDips]))
678  self.AllResults[astrm]['L'].append(len(Energies[0]))
679  self.AllResults[astrm]['Steps'].append(self.liquid_md_steps)
680 
681  if len(mPoints) > 0:
682  self.AllResults[astrm]['mE'].append(np.array([i for pt, i in zip(Points,mEnergies) if pt in mPoints]))
683  self.AllResults[astrm]['mG'].append(np.array([i for pt, i in zip(Points,mGrads) if pt in mPoints]))
684 
685  os.chdir(cwd)
686 
687  return self.get(mvals, AGrad, AHess)
688 
689  def get(self, mvals, AGrad=True, AHess=True):
690  """ Wrapper of self.get_normal() and self.get_pure_num_grad() """
691  if self.pure_num_grad:
692  property_results = self.get_pure_num_grad(mvals, AGrad=AGrad, AHess=AHess)
693  else:
694  property_results = self.get_normal(mvals, AGrad=AGrad, AHess=AHess)
695  return self.form_get_result(property_results, AGrad=AGrad, AHess=AHess)
696 
697  def get_normal(self, mvals, AGrad=True, AHess=True):
698 
699  """
700  Fitting of liquid bulk properties. This is the current major
701  direction of development for ForceBalance. Basically, fitting
702  the QM energies / forces alone does not always give us the
703  best simulation behavior. In many cases it makes more sense
704  to try and reproduce some experimentally known data as well.
705 
706  In order to reproduce experimentally known data, we need to
707  run a simulation and compare the simulation result to
708  experiment. The main challenge here is that the simulations
709  are computationally intensive (i.e. they require energy and
710  force evaluations), and furthermore the results are noisy. We
711  need to run the simulations automatically and remotely
712  (i.e. on clusters) and a good way to calculate the derivatives
713  of the simulation results with respect to the parameter values.
714 
715  This function contains some experimentally known values of the
716  density and enthalpy of vaporization (Hvap) of liquid water.
717  It launches the density and Hvap calculations on the cluster,
718  and gathers the results / derivatives. The actual calculation
719  of results / derivatives is done in a separate file.
720 
721  After the results come back, they are gathered together to form
722  an objective function.
723 
724  @param[in] mvals Mathematical parameter values
725  @param[in] AGrad Switch to turn on analytic gradient
726  @param[in] AHess Switch to turn on analytic Hessian
727  @return property_results
728 
729  """
730 
731  unpack = lp_load('forcebalance.p')
732  mvals1 = unpack[1]
733  if len(mvals) > 0 and (np.max(np.abs(mvals1 - mvals)) > 1e-3):
734  warn_press_key("mvals from forcebalance.p does not match up with internal values! (Are you reading data from a previous run?)\nmvals(call)=%s mvals(disk)=%s" % (mvals, mvals1))
735 
736  mbar_verbose = False
737 
738  Answer = {}
739 
740  Results = {}
741  Points = [] # These are the phase points for which data exists.
742  BPoints = [] # These are the phase points for which we are doing MBAR for the condensed phase.
743  mBPoints = [] # These are the phase points for which we are doing MBAR for the monomers.
744  mPoints = [] # These are the phase points to use for enthalpy of vaporization; if we're scanning pressure then set hvap_wt for higher pressures to zero.
745  stResults = {} # Storing the results from the NVT run for surface tension
746  tt = 0
747  for label, PT in zip(self.Labels, self.PhasePoints):
748  if os.path.exists('./%s/npt_result.p' % label):
749  logger.info('Reading information from ./%s/npt_result.p\n' % label)
750  Points.append(PT)
751  Results[tt] = lp_load('./%s/npt_result.p' % label)
752  if 'hvap' in self.RefData and PT[0] not in [i[0] for i in mPoints]:
753  mPoints.append(PT)
754  if 'mbar' in self.RefData and PT in self.RefData['mbar'] and self.RefData['mbar'][PT]:
755  BPoints.append(PT)
756  if 'hvap' in self.RefData and PT[0] not in [i[0] for i in mBPoints]:
757  mBPoints.append(PT)
758  if 'surf_ten' in self.RefData and PT in self.RefData['surf_ten']:
759  if os.path.exists('./%s/nvt_result.p' % label):
760  stResults[PT] = lp_load('./%s/nvt_result.p' % label)
761  else:
762  logger.warning('In %s :\n' % os.getcwd())
763  logger.warning('The file ./%s/nvt_result.p does not exist so we cannot read it\n' % label)
764  pass
765  tt += 1
766  else:
767  logger.warning('In %s :\n' % os.getcwd())
768  logger.warning('The file ./%s/npt_result.p does not exist so we cannot read it\n' % label)
769  pass
770  if len(Points) == 0:
771  logger.error('The liquid simulations have terminated with \x1b[1;91mno readable data\x1b[0m - this is a problem!\n')
772  raise RuntimeError
773 
774  # Having only one simulation for MBAR is the same as not doing MBAR at all.
775  if len(BPoints) == 1:
776  BPoints = []
777  if len(mBPoints) == 1:
778  mBPoints = []
779 
780  # Assign variable names to all the stuff in npt_result.p
781  Rhos, Vols, Potentials, Energies, Dips, Grads, GDips, mPotentials, mEnergies, mGrads, \
782  Rho_errs, Hvap_errs, Alpha_errs, Kappa_errs, Cp_errs, Eps0_errs, NMols = ([Results[t][i] for t in range(len(Points))] for i in range(17))
783  # Determine the number of molecules
784  if len(set(NMols)) != 1:
785  logger.error(str(NMols))
786  logger.error('The above list should only contain one number - the number of molecules\n')
787  raise RuntimeError
788  else:
789  NMol = list(set(NMols))[0]
790 
791  if not self.adapt_errors:
792  self.AllResults = defaultdict(lambda:defaultdict(list))
793 
794  astrm = astr(mvals)
795  if len(Points) != len(self.Labels):
796  logger.info("Data sets is not full, will not use for concatenation.")
797  astrm += "_"*(Counter()+1)
798  self.AllResults[astrm]['Pts'].append(Points)
799  self.AllResults[astrm]['mPts'].append(Points)
800  self.AllResults[astrm]['E'].append(np.array(Energies))
801  self.AllResults[astrm]['V'].append(np.array(Vols))
802  self.AllResults[astrm]['R'].append(np.array(Rhos))
803  self.AllResults[astrm]['Dx'].append(np.array([d[:,0] for d in Dips]))
804  self.AllResults[astrm]['Dy'].append(np.array([d[:,1] for d in Dips]))
805  self.AllResults[astrm]['Dz'].append(np.array([d[:,2] for d in Dips]))
806  self.AllResults[astrm]['G'].append(np.array(Grads))
807  self.AllResults[astrm]['GDx'].append(np.array([gd[0] for gd in GDips]))
808  self.AllResults[astrm]['GDy'].append(np.array([gd[1] for gd in GDips]))
809  self.AllResults[astrm]['GDz'].append(np.array([gd[2] for gd in GDips]))
810  self.AllResults[astrm]['L'].append(len(Energies[0]))
811  self.AllResults[astrm]['Steps'].append(self.liquid_md_steps)
812 
813  if len(mPoints) > 0:
814  mE_idx = 0
815  # QYD: here we use a dictionary to store the index in mE/mG, which is different with the index in Points
816  mE_idx_dict = dict()
817  all_mEs, all_mGs = [], []
818  for pt, mEs, mGs in zip(Points, mEnergies, mGrads):
819  if pt in mPoints:
820  all_mEs.append(mEs)
821  all_mGs.append(mGs)
822  mE_idx_dict[pt] = mE_idx
823  mE_idx += 1
824  self.AllResults[astrm]['mE'].append(np.array(all_mEs))
825  self.AllResults[astrm]['mG'].append(np.array(all_mGs))
826 
827  # Number of data sets belonging to this value of the parameters.
828  Nrpt = len(self.AllResults[astrm]['R'])
829  sumsteps = sum(self.AllResults[astrm]['Steps'])
830  if self.liquid_md_steps != sumsteps:
831  printcool("This objective function evaluation combines %i datasets\n" \
832  "Increasing simulation length: %i -> %i steps" % \
833  (Nrpt, self.liquid_md_steps, sumsteps), color=6)
834  if self.liquid_md_steps * 2 != sumsteps:
835  logger.error("Spoo!\n")
836  raise RuntimeError
837  self.liquid_eq_steps *= 2
838  self.liquid_md_steps *= 2
839  self.gas_eq_steps *= 2
840  self.gas_md_steps *= 2
841 
842  # Concatenate along the data-set axis (more than 1 element if we've returned to these parameters.)
843  E, V, R, Dx, Dy, Dz = \
844  (np.hstack(tuple(self.AllResults[astrm][i])) for i in \
845  ['E', 'V', 'R', 'Dx', 'Dy', 'Dz'])
846 
847  G, GDx, GDy, GDz = \
848  (np.hstack((np.concatenate(tuple(self.AllResults[astrm][i]), axis=2))) for i in ['G', 'GDx', 'GDy', 'GDz'])
849 
850  if len(mPoints) > 0:
851  mE = np.hstack(tuple(self.AllResults[astrm]['mE']))
852  mG = np.hstack((np.concatenate(tuple(self.AllResults[astrm]['mG']), axis=2)))
853  Rho_calc = OrderedDict([])
854  Rho_grad = OrderedDict([])
855  Rho_std = OrderedDict([])
856  Hvap_calc = OrderedDict([])
857  Hvap_grad = OrderedDict([])
858  Hvap_std = OrderedDict([])
859  Alpha_calc = OrderedDict([])
860  Alpha_grad = OrderedDict([])
861  Alpha_std = OrderedDict([])
862  Kappa_calc = OrderedDict([])
863  Kappa_grad = OrderedDict([])
864  Kappa_std = OrderedDict([])
865  Cp_calc = OrderedDict([])
866  Cp_grad = OrderedDict([])
867  Cp_std = OrderedDict([])
868  Eps0_calc = OrderedDict([])
869  Eps0_grad = OrderedDict([])
870  Eps0_std = OrderedDict([])
871  Surf_ten_calc = OrderedDict([])
872  Surf_ten_grad = OrderedDict([])
873  Surf_ten_std = OrderedDict([])
874 
875  # The unit that converts atmospheres * nm**3 into kj/mol :)
876  pvkj=0.061019351687175
877 
878  # Run MBAR using the total energies. Required for estimates that use the kinetic energy.
879  BSims = len(BPoints)
880  Shots = len(E[0])
881  N_k = np.ones(BSims, dtype=int)*Shots
882  # Use the value of the energy for snapshot t from simulation k at potential m
883  U_kln = np.zeros([BSims,BSims,Shots])
884  for m, PT in enumerate(BPoints):
885  T = PT[0]
886  P = PT[1] / 1.01325 if PT[2] == 'bar' else PT[1]
887  beta = 1. / (kb * T)
888  for k in range(BSims):
889  # The correct Boltzmann factors include PV.
890  # Note that because the Boltzmann factors are computed from the conditions at simulation "m",
891  # the pV terms must be rescaled to the pressure at simulation "m".
892  kk = Points.index(BPoints[k])
893  U_kln[k, m, :] = E[kk] + P*V[kk]*pvkj
894  U_kln[k, m, :] *= beta
895  W1 = None
896  if len(BPoints) > 1:
897  logger.info("Running MBAR analysis on %i states...\n" % len(BPoints))
898  mbar = pymbar.MBAR(U_kln, N_k, verbose=mbar_verbose, relative_tolerance=5.0e-8)
899  W1 = mbar.getWeights()
900  logger.info("Done\n")
901  elif len(BPoints) == 1:
902  W1 = np.ones((Shots,1))
903  W1 /= Shots
904 
905  def fill_weights(weights, phase_points, mbar_points, snapshots):
906  """ Fill in the weight matrix with MBAR weights where MBAR was run,
907  and equal weights otherwise. """
908  new_weights = np.zeros([len(phase_points)*snapshots,len(phase_points)])
909  for m, PT in enumerate(phase_points):
910  if PT in mbar_points:
911  mm = mbar_points.index(PT)
912  for kk, PT1 in enumerate(mbar_points):
913  k = phase_points.index(PT1)
914  logger.debug("Will fill W2[%i:%i,%i] with W1[%i:%i,%i]\n" % (k*snapshots,k*snapshots+snapshots,m,kk*snapshots,kk*snapshots+snapshots,mm))
915  new_weights[k*snapshots:(k+1)*snapshots,m] = weights[kk*snapshots:(kk+1)*snapshots,mm]
916  else:
917  logger.debug("Will fill W2[%i:%i,%i] with equal weights\n" % (m*snapshots,(m+1)*snapshots,m))
918  new_weights[m*snapshots:(m+1)*snapshots,m] = 1.0/snapshots
919  return new_weights
920 
921  W2 = fill_weights(W1, Points, BPoints, Shots)
922 
923  if len(mPoints) > 0:
924  # Run MBAR on the monomers. This is barely necessary.
925  mW1 = None
926  mShots = len(mE[0])
927  if len(mBPoints) > 1:
928  mBSims = len(mBPoints)
929  mN_k = np.ones(mBSims, dtype=int)*mShots
930  mU_kln = np.zeros([mBSims,mBSims,mShots])
931  for m, PT in enumerate(mBPoints):
932  T = PT[0]
933  beta = 1. / (kb * T)
934  for k in range(mBSims):
935  mE_idx = mE_idx_dict[mBPoints[k]]
936  mU_kln[k, m, :] = mE[mE_idx]
937  mU_kln[k, m, :] *= beta
938  if np.abs(np.std(mE)) > 1e-6 and mBSims > 1:
939  mmbar = pymbar.MBAR(mU_kln, mN_k, verbose=False, relative_tolerance=5.0e-8, method='self-consistent-iteration')
940  mW1 = mmbar.getWeights()
941  elif len(mBPoints) == 1:
942  mW1 = np.ones((mShots,1))
943  mW1 /= mShots
944  mW2 = fill_weights(mW1, mPoints, mBPoints, mShots)
945 
946  if self.do_self_pol:
947  EPol = self.polarization_correction(mvals)
948  GEPol = np.array([(f12d3p(fdwrap(self.polarization_correction, mvals, p), h = self.h, f0 = EPol)[0] if p in self.pgrad else 0.0) for p in range(self.FF.np)])
949  bar = printcool("Self-polarization correction to \nenthalpy of vaporization is % .3f kJ/mol%s" % (EPol, ", Derivative:" if AGrad else ""))
950  if AGrad:
951  self.FF.print_map(vals=GEPol)
952  logger.info(bar)
953 
954  # Arrays must be flattened now for calculation of properties.
955  E = E.flatten()
956  V = V.flatten()
957  R = R.flatten()
958  Dx = Dx.flatten()
959  Dy = Dy.flatten()
960  Dz = Dz.flatten()
961  if len(mPoints) > 0: mE = mE.flatten()
962 
963  for i, PT in enumerate(Points):
964  T = PT[0]
965  P = PT[1] / 1.01325 if PT[2] == 'bar' else PT[1]
966  PV = P*V*pvkj
967  H = E + PV
968  # The weights that we want are the last ones.
969  W = flat(W2[:,i])
970  C = weight_info(W, PT, np.ones(len(Points), dtype=int)*Shots, verbose=mbar_verbose)
971  Gbar = flat(np.dot(G, col(W)))
972  mBeta = -1/kb/T
973  Beta = 1/kb/T
974  kT = kb*T
975  # Define some things to make the analytic derivatives easier.
976  def avg(vec):
977  return np.dot(W,vec)
978  def covde(vec):
979  return flat(np.dot(G, col(W*vec))) - avg(vec)*Gbar
980  def deprod(vec):
981  return flat(np.dot(G, col(W*vec)))
982 
983  Rho_calc[PT] = np.dot(W,R)
984  Rho_grad[PT] = mBeta*(flat(np.dot(G, col(W*R))) - np.dot(W,R)*Gbar)
985 
986  if PT in mPoints:
987  ii = mPoints.index(PT)
988  mW = flat(mW2[:,ii])
989  mGbar = flat(np.dot(mG, col(mW)))
990  Hvap_calc[PT] = np.dot(mW,mE) - np.dot(W,E)/NMol + kb*T - np.dot(W, PV)/NMol
991  Hvap_grad[PT] = mGbar + mBeta*(flat(np.dot(mG,col(mW*mE))) - np.dot(mW,mE)*mGbar)
992  Hvap_grad[PT] -= (Gbar + mBeta*(flat(np.dot(G,col(W*E))) - np.dot(W,E)*Gbar)) / NMol
993  Hvap_grad[PT] -= (mBeta*(flat(np.dot(G,col(W*PV))) - np.dot(W,PV)*Gbar)) / NMol
994  if self.do_self_pol:
995  Hvap_calc[PT] -= EPol
996  Hvap_grad[PT] -= GEPol
997  if hasattr(self,'use_cni') and self.use_cni:
998  if not ('cni' in self.RefData and self.RefData['cni'][PT]):
999  logger.error('Asked for a nonideality correction but not provided in reference data (data.csv). Either disable the option in data.csv or add data.\n')
1000  raise RuntimeError
1001  logger.debug("Adding % .3f to enthalpy of vaporization at " % self.RefData['cni'][PT] + str(PT) + '\n')
1002  Hvap_calc[PT] += self.RefData['cni'][PT]
1003  if hasattr(self,'use_cvib_intra') and self.use_cvib_intra:
1004  if not ('cvib_intra' in self.RefData and self.RefData['cvib_intra'][PT]):
1005  logger.error('Asked for a quantum intramolecular vibrational correction but not provided in reference data (data.csv). Either disable the option in data.csv or add data.\n')
1006  raise RuntimeError
1007  logger.debug("Adding % .3f to enthalpy of vaporization at " % self.RefData['cvib_intra'][PT] + str(PT) + '\n')
1008  Hvap_calc[PT] += self.RefData['cvib_intra'][PT]
1009  if hasattr(self,'use_cvib_inter') and self.use_cvib_inter:
1010  if not ('cvib_inter' in self.RefData and self.RefData['cvib_inter'][PT]):
1011  logger.error('Asked for a quantum intermolecular vibrational correction but not provided in reference data (data.csv). Either disable the option in data.csv or add data.\n')
1012  raise RuntimeError
1013  logger.debug("Adding % .3f to enthalpy of vaporization at " % self.RefData['cvib_inter'][PT] + str(PT) + '\n')
1014  Hvap_calc[PT] += self.RefData['cvib_inter'][PT]
1015  else:
1016  Hvap_calc[PT] = 0.0
1017  Hvap_grad[PT] = np.zeros(self.FF.np)
1018 
1019  Alpha_calc[PT] = 1e4 * (avg(H*V)-avg(H)*avg(V))/avg(V)/(kT*T)
1020  GAlpha1 = -1 * Beta * deprod(H*V) * avg(V) / avg(V)**2
1021  GAlpha2 = +1 * Beta * avg(H*V) * deprod(V) / avg(V)**2
1022  GAlpha3 = deprod(V)/avg(V) - Gbar
1023  GAlpha4 = Beta * covde(H)
1024  Alpha_grad[PT] = 1e4 * (GAlpha1 + GAlpha2 + GAlpha3 + GAlpha4)/(kT*T)
1025 
1026  bar_unit = 0.06022141793 * 1e6
1027  Kappa_calc[PT] = bar_unit / kT * (avg(V**2)-avg(V)**2)/avg(V)
1028  GKappa1 = +1 * Beta**2 * avg(V**2) * deprod(V) / avg(V)**2
1029  GKappa2 = -1 * Beta**2 * avg(V) * deprod(V**2) / avg(V)**2
1030  GKappa3 = +1 * Beta**2 * covde(V)
1031  Kappa_grad[PT] = bar_unit*(GKappa1 + GKappa2 + GKappa3)
1032 
1033  Cp_calc[PT] = 1000/(4.184*NMol*kT*T) * (avg(H**2) - avg(H)**2)
1034  if hasattr(self,'use_cvib_intra') and self.use_cvib_intra:
1035  logger.debug("Adding " + str(self.RefData['devib_intra'][PT]) + " to the heat capacity\n")
1036  Cp_calc[PT] += self.RefData['devib_intra'][PT]
1037  if hasattr(self,'use_cvib_inter') and self.use_cvib_inter:
1038  logger.debug("Adding " + str(self.RefData['devib_inter'][PT]) + " to the heat capacity\n")
1039  Cp_calc[PT] += self.RefData['devib_inter'][PT]
1040  GCp1 = 2*covde(H) * 1000 / 4.184 / (NMol*kT*T)
1041  GCp2 = mBeta*covde(H**2) * 1000 / 4.184 / (NMol*kT*T)
1042  GCp3 = 2*Beta*avg(H)*covde(H) * 1000 / 4.184 / (NMol*kT*T)
1043  Cp_grad[PT] = GCp1 + GCp2 + GCp3
1044 
1045  prefactor = 30.348705333964077
1046  D2 = avg(Dx**2)+avg(Dy**2)+avg(Dz**2)-avg(Dx)**2-avg(Dy)**2-avg(Dz)**2
1047  Eps0_calc[PT] = 1.0 + prefactor*(D2/avg(V))/T
1048  GD2 = 2*(flat(np.dot(GDx,col(W*Dx))) - avg(Dx)*flat(np.dot(GDx,col(W)))) - Beta*(covde(Dx**2) - 2*avg(Dx)*covde(Dx))
1049  GD2 += 2*(flat(np.dot(GDy,col(W*Dy))) - avg(Dy)*flat(np.dot(GDy,col(W)))) - Beta*(covde(Dy**2) - 2*avg(Dy)*covde(Dy))
1050  GD2 += 2*(flat(np.dot(GDz,col(W*Dz))) - avg(Dz)*flat(np.dot(GDz,col(W)))) - Beta*(covde(Dz**2) - 2*avg(Dz)*covde(Dz))
1051  Eps0_grad[PT] = prefactor*(GD2/avg(V) - mBeta*covde(V)*D2/avg(V)**2)/T
1052 
1053  if PT in stResults:
1054  Surf_ten_calc[PT] = stResults[PT]["surf_ten"]
1055  Surf_ten_grad[PT] = stResults[PT]["G_surf_ten"]
1056  Surf_ten_std[PT] = stResults[PT]["surf_ten_err"]
1057 
1058  Rho_std[PT] = np.sqrt(sum(C**2 * np.array(Rho_errs)**2))
1059  if PT in mPoints:
1060  Hvap_std[PT] = np.sqrt(sum(C**2 * np.array(Hvap_errs)**2))
1061  else:
1062  Hvap_std[PT] = 0.0
1063  Alpha_std[PT] = np.sqrt(sum(C**2 * np.array(Alpha_errs)**2)) * 1e4
1064  Kappa_std[PT] = np.sqrt(sum(C**2 * np.array(Kappa_errs)**2)) * 1e6
1065  Cp_std[PT] = np.sqrt(sum(C**2 * np.array(Cp_errs)**2))
1066  Eps0_std[PT] = np.sqrt(sum(C**2 * np.array(Eps0_errs)**2))
1067 
1068  property_results = dict()
1069  property_results['rho'] = Rho_calc, Rho_std, Rho_grad
1070  property_results['hvap'] = Hvap_calc, Hvap_std, Hvap_grad
1071  property_results['alpha'] = Alpha_calc, Alpha_std, Alpha_grad
1072  property_results['kappa'] = Kappa_calc, Kappa_std, Kappa_grad
1073  property_results['cp'] = Cp_calc, Cp_std, Cp_grad
1074  property_results['eps0'] = Eps0_calc, Eps0_std, Eps0_grad
1075  property_results['surf_ten'] = Surf_ten_calc, Surf_ten_std, Surf_ten_grad
1076  return property_results
1077 
1078  def get_pure_num_grad(self, mvals, AGrad=True, AHess=True):
1079  """
1080  This function calls self.get_normal(AGrad=False) to get the property values and std_err,
1081  but compute the property gradients using finite difference of the FF parameters.
1082 
1083  @param[in] mvals Mathematical parameter values
1084  @param[in] AGrad Switch to turn on analytic gradient
1085  @param[in] AHess Switch to turn on analytic Hessian
1086  @return property_results
1087 
1088  """
1089  if not self.pure_num_grad:
1090  raise RuntimeError("Not running in pure numerical gradients mode. Please use self.get_normal() instead!")
1091 
1092  if not AGrad:
1093  return self.get_normal(mvals, AGrad=AGrad, AHess=AHess)
1094 
1095  # Read the original property results
1096  property_results = self.get_normal(mvals, AGrad=False, AHess=False)
1097  # Update the gradients of each property with the finite differences from simulations
1098  logger.info("Pure numerical gradient mode: loading property values from sub-directorys.\n")
1099  # The folder structure should be consistent with self.submit_jobs()
1100  for i_m in range(len(mvals)):
1101  property_results_pm = dict()
1102  for delta_m in [+self.liquid_fdiff_h, -self.liquid_fdiff_h]:
1103  pure_num_grad_label = 'mvals_%03d_%f' % (i_m, delta_m)
1104  logger.info("Reading from sub-directory %s\n" % pure_num_grad_label)
1105  os.chdir(pure_num_grad_label)
1106  # copy the original mvals and perturb
1107  new_mvals = copy.copy(mvals)
1108  new_mvals[i_m] += delta_m
1109  # reset self.AllResults?
1110  #self.AllResults = defaultdict(lambda:defaultdict(list))
1111  property_results_pm[delta_m] = self.get_normal(new_mvals, AGrad=False, AHess=False)
1112  os.chdir('..')
1113  for key in property_results:
1114  for PT in property_results[key][2].keys():
1115  property_results[key][2][PT][i_m] = (property_results_pm[+self.liquid_fdiff_h][key][0][PT] - property_results_pm[-self.liquid_fdiff_h][key][0][PT]) / (2.0*self.liquid_fdiff_h)
1116 
1117  return property_results
1118 
1119  def form_get_result(self, property_results, AGrad=True, AHess=True):
1120  """
1121  This function takes the property_results from get_normal() or get_pure_num_grad()
1122  and form the answer for the return of the self.get() function
1123 
1124  @in property_results
1125  @return Answer Contribution to the objective function
1126 
1127  """
1128 
1129  Rho_calc, Rho_std, Rho_grad = property_results['rho']
1130  Hvap_calc, Hvap_std, Hvap_grad = property_results['hvap']
1131  Alpha_calc, Alpha_std, Alpha_grad = property_results['alpha']
1132  Kappa_calc, Kappa_std, Kappa_grad = property_results['kappa']
1133  Cp_calc, Cp_std, Cp_grad = property_results['cp']
1134  Eps0_calc, Eps0_std, Eps0_grad = property_results['eps0']
1135  Surf_ten_calc, Surf_ten_std, Surf_ten_grad = property_results['surf_ten']
1136 
1137  Points = list(Rho_calc.keys())
1139  # Get contributions to the objective function
1140  X_Rho, G_Rho, H_Rho, RhoPrint = self.objective_term(Points, 'rho', Rho_calc, Rho_std, Rho_grad, name="Density")
1141  X_Hvap, G_Hvap, H_Hvap, HvapPrint = self.objective_term(Points, 'hvap', Hvap_calc, Hvap_std, Hvap_grad, name="H_vap", SubAverage=self.hvap_subaverage)
1142  X_Alpha, G_Alpha, H_Alpha, AlphaPrint = self.objective_term(Points, 'alpha', Alpha_calc, Alpha_std, Alpha_grad, name="Thermal Expansion")
1143  X_Kappa, G_Kappa, H_Kappa, KappaPrint = self.objective_term(Points, 'kappa', Kappa_calc, Kappa_std, Kappa_grad, name="Compressibility")
1144  X_Cp, G_Cp, H_Cp, CpPrint = self.objective_term(Points, 'cp', Cp_calc, Cp_std, Cp_grad, name="Heat Capacity")
1145  X_Eps0, G_Eps0, H_Eps0, Eps0Print = self.objective_term(Points, 'eps0', Eps0_calc, Eps0_std, Eps0_grad, name="Dielectric Constant")
1146  X_Surf_ten, G_Surf_ten, H_Surf_ten, Surf_tenPrint = self.objective_term(list(Surf_ten_calc.keys()), 'surf_ten', Surf_ten_calc, Surf_ten_std, Surf_ten_grad, name="Surface Tension")
1147 
1148  Gradient = np.zeros(self.FF.np)
1149  Hessian = np.zeros((self.FF.np,self.FF.np))
1150 
1151  if X_Rho == 0: self.w_rho = 0.0
1152  if X_Hvap == 0: self.w_hvap = 0.0
1153  if X_Alpha == 0: self.w_alpha = 0.0
1154  if X_Kappa == 0: self.w_kappa = 0.0
1155  if X_Cp == 0: self.w_cp = 0.0
1156  if X_Eps0 == 0: self.w_eps0 = 0.0
1157  if X_Surf_ten == 0: self.w_surf_ten = 0.0
1158 
1159  if self.w_normalize:
1160  w_tot = self.w_rho + self.w_hvap + self.w_alpha + self.w_kappa + self.w_cp + self.w_eps0 + self.w_surf_ten
1161  else:
1162  w_tot = 1.0
1163  w_1 = self.w_rho / w_tot
1164  w_2 = self.w_hvap / w_tot
1165  w_3 = self.w_alpha / w_tot
1166  w_4 = self.w_kappa / w_tot
1167  w_5 = self.w_cp / w_tot
1168  w_6 = self.w_eps0 / w_tot
1169  w_7 = self.w_surf_ten / w_tot
1170 
1171  Objective = w_1 * X_Rho + w_2 * X_Hvap + w_3 * X_Alpha + w_4 * X_Kappa + w_5 * X_Cp + w_6 * X_Eps0 + w_7 * X_Surf_ten
1172  if AGrad:
1173  Gradient = w_1 * G_Rho + w_2 * G_Hvap + w_3 * G_Alpha + w_4 * G_Kappa + w_5 * G_Cp + w_6 * G_Eps0 + w_7 * G_Surf_ten
1174  if AHess:
1175  Hessian = w_1 * H_Rho + w_2 * H_Hvap + w_3 * H_Alpha + w_4 * H_Kappa + w_5 * H_Cp + w_6 * H_Eps0 + w_7 * H_Surf_ten
1176 
1177  if not in_fd():
1178  self.Xp = {"Rho" : X_Rho, "Hvap" : X_Hvap, "Alpha" : X_Alpha,
1179  "Kappa" : X_Kappa, "Cp" : X_Cp, "Eps0" : X_Eps0, "Surf_ten": X_Surf_ten}
1180  self.Wp = {"Rho" : w_1, "Hvap" : w_2, "Alpha" : w_3,
1181  "Kappa" : w_4, "Cp" : w_5, "Eps0" : w_6, "Surf_ten" : w_7}
1182  self.Pp = {"Rho" : RhoPrint, "Hvap" : HvapPrint, "Alpha" : AlphaPrint,
1183  "Kappa" : KappaPrint, "Cp" : CpPrint, "Eps0" : Eps0Print, "Surf_ten": Surf_tenPrint}
1184  if AGrad:
1185  self.Gp = {"Rho" : G_Rho, "Hvap" : G_Hvap, "Alpha" : G_Alpha,
1186  "Kappa" : G_Kappa, "Cp" : G_Cp, "Eps0" : G_Eps0, "Surf_ten": G_Surf_ten}
1187  self.Objective = Objective
1188 
1189  Answer = {'X':Objective, 'G':Gradient, 'H':Hessian}
1190  return Answer
def get_pure_num_grad(self, mvals, AGrad=True, AHess=True)
This function calls self.get_normal(AGrad=False) to get the property values and std_err, but compute the property gradients using finite difference of the FF parameters.
Definition: liquid.py:1098
def lp_load(fnm)
Read an object from a bzipped file specified by the path.
Definition: nifty.py:836
Nifty functions, intended to be imported by any module within ForceBalance.
MBarEnergy
Evaluated energies for all trajectories (i.e.
Definition: liquid.py:229
Optimization algorithms.
def LinkFile(src, dest, nosrcok=False)
Definition: nifty.py:1313
def flat(vec)
Given any list, array, or matrix, return a single-index array.
Definition: nifty.py:467
def polarization_correction(self, mvals)
Definition: liquid.py:420
def objective_term(self, points, expname, calc, err, grad, name="Quantity", SubAverage=False)
Definition: liquid.py:466
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 queue_up(wq, command, input_files, output_files, tag=None, tgt=None, verbose=True, print_time=60)
Submit a job to the Work Queue.
Definition: nifty.py:941
def lp_dump(obj, fnm, protocol=0)
Write an object to a zipped pickle file specified by the path.
Definition: nifty.py:817
def col(vec)
Given any list, array, or matrix, return a 1-column 2D array.
Definition: nifty.py:448
def get(self, mvals, AGrad=True, AHess=True)
Wrapper of self.get_normal() and self.get_pure_num_grad()
Definition: liquid.py:697
def link_dir_contents(abssrcdir, absdestdir)
Definition: nifty.py:1345
def nvt_simulation(self, temperature)
Submit a NVT simulation to the Work Queue.
Definition: liquid.py:399
Subclass of Target for liquid property matching.
Definition: liquid.py:91
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 __init__(self, options, tgt_opts, forcefield)
Definition: liquid.py:94
def form_get_result(self, property_results, AGrad=True, AHess=True)
This function takes the property_results from get_normal() or get_pure_num_grad() and form the answer...
Definition: liquid.py:1138
def npt_simulation(self, temperature, pressure, simnum)
Submit a NPT simulation to the Work Queue.
Definition: liquid.py:374
def read(self, mvals, AGrad=True, AHess=True)
Read in time series for all previous iterations.
Definition: liquid.py:621
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
surf_ten_mol
Read the reference data.
Definition: liquid.py:213
def submit_jobs(self, mvals, AGrad=True, AHess=True)
Definition: liquid.py:546
SavedTraj
Saved trajectories for all iterations and all temperatures.
Definition: liquid.py:227
def get_normal(self, mvals, AGrad=True, AHess=True)
Fitting of liquid bulk properties.
Definition: liquid.py:736
def weight_info(W, PT, N_k, verbose=True, PTS=None)
Definition: liquid.py:38
def prepare_temp_directory(self)
Prepare the temporary directory by copying in important files.
Definition: liquid.py:250
AllResults
Saved results for all iterations self.SavedMVals = [].
Definition: liquid.py:232
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
def post_init(self, options)
Definition: liquid.py:234
def astr(vec1d, precision=4)
Write an array to a string so we can use it to key a dictionary.
Definition: nifty.py:207
loop_over_snapshots
LPW 2018-02-11: This is set to True if the target calculates a single-point property over several exi...
Definition: liquid.py:172
def isfloat(word)
Matches ANY number; it can be a decimal, scientific notation, integer, or what have you...
Definition: molecule.py:406
def indicate(self)
Definition: liquid.py:439
def Counter()
Definition: optimizer.py:35
def wopen(dest, binary=False)
If trying to write to a symbolic link, remove it first.
Definition: nifty.py:1304
def getWorkQueue()
Definition: nifty.py:904
def check_files(self, there)
Definition: liquid.py:359
def read_data(self)
Definition: liquid.py:257