ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
objective.py
Go to the documentation of this file.
1 """@package forcebalance.objective
2 
3 ForceBalance objective function."""
4 from __future__ import division
5 
6 from builtins import range
7 from builtins import object
8 import sys
9 import inspect
10 #from implemented import Implemented_Targets
11 import numpy as np
12 from collections import defaultdict, OrderedDict
13 import forcebalance
14 from forcebalance.finite_difference import in_fd
15 from forcebalance.nifty import printcool_dictionary, createWorkQueue, getWorkQueue, wq_wait
16 import datetime
17 import traceback
18 from forcebalance.output import getLogger
19 logger = getLogger(__name__)
20 
21 try:
22  from forcebalance.gmxio import AbInitio_GMX, BindingEnergy_GMX, Liquid_GMX, Lipid_GMX, Interaction_GMX, Moments_GMX, Vibration_GMX, Thermo_GMX
23 except:
24  logger.warning(traceback.format_exc())
25  logger.warning("Gromacs module import failed\n")
26 
27 try:
28  from forcebalance.tinkerio import AbInitio_TINKER, Vibration_TINKER, BindingEnergy_TINKER, Moments_TINKER, Interaction_TINKER, Liquid_TINKER
29 except:
30  logger.warning(traceback.format_exc())
31  logger.warning("Tinker module import failed\n")
32 
33 try:
34  from forcebalance.openmmio import AbInitio_OpenMM, Liquid_OpenMM, Interaction_OpenMM, BindingEnergy_OpenMM, Moments_OpenMM, Hydration_OpenMM, Vibration_OpenMM, OptGeoTarget_OpenMM, TorsionProfileTarget_OpenMM
35 except:
36  logger.warning(traceback.format_exc())
37  logger.warning("OpenMM module import failed; check OpenMM package\n")
38 
39 try:
40  from forcebalance.smirnoffio import AbInitio_SMIRNOFF, Liquid_SMIRNOFF, Vibration_SMIRNOFF, OptGeoTarget_SMIRNOFF, TorsionProfileTarget_SMIRNOFF, smirnoff_analyze_parameter_coverage
41 except:
42  logger.warning(traceback.format_exc())
43  logger.warning("SMIRNOFF module import failed; check SMIRNOFF package\n")
44 
45 try:
46  from forcebalance.evaluator_io import Evaluator_SMIRNOFF
47 except:
48  logger.warning(traceback.format_exc())
49  logger.warning("openff.evaluator module import failed\n")
50 
51 try:
52  from forcebalance.abinitio_internal import AbInitio_Internal
53 except:
54  logger.warning(traceback.format_exc())
55  logger.warning("Internal energy fitting module import failed\n")
56 
57 try:
58  from forcebalance.counterpoise import Counterpoise
59 except:
60  logger.warning(traceback.format_exc())
61  logger.warning("Counterpoise module import failed\n")
62 
63 try:
64  from forcebalance.amberio import AbInitio_AMBER, Interaction_AMBER, Vibration_AMBER, Liquid_AMBER
65 except:
66  logger.warning(traceback.format_exc())
67  logger.warning("Amber module import failed\n")
68 
69 try:
70  from forcebalance.psi4io import THCDF_Psi4, RDVR3_Psi4
71 except:
72  logger.warning(traceback.format_exc())
73  logger.warning("PSI4 module import failed\n")
74 
75 try:
76  from forcebalance.target import RemoteTarget
77 except:
78  logger.warning(traceback.format_exc())
79  logger.warning("Remote Target import failed\n")
80 
81 
82 Implemented_Targets = {
83  'ABINITIO_GMX':AbInitio_GMX,
84  'ABINITIO_TINKER':AbInitio_TINKER,
85  'ABINITIO_OPENMM':AbInitio_OpenMM,
86  'ABINITIO_SMIRNOFF':AbInitio_SMIRNOFF,
87  'ABINITIO_AMBER':AbInitio_AMBER,
88  'ABINITIO_INTERNAL':AbInitio_Internal,
89  'VIBRATION_TINKER':Vibration_TINKER,
90  'VIBRATION_GMX':Vibration_GMX,
91  'VIBRATION_AMBER':Vibration_AMBER,
92  'VIBRATION_OPENMM':Vibration_OpenMM,
93  'VIBRATION_SMIRNOFF': Vibration_SMIRNOFF,
94  'THERMO_GMX':Thermo_GMX,
95  'LIQUID_OPENMM':Liquid_OpenMM,
96  'LIQUID_SMIRNOFF':Liquid_SMIRNOFF,
97  'LIQUID_TINKER':Liquid_TINKER,
98  'LIQUID_GMX':Liquid_GMX,
99  'LIQUID_AMBER':Liquid_AMBER,
100  'LIPID_GMX':Lipid_GMX,
101  'COUNTERPOISE':Counterpoise,
102  'THCDF_PSI4':THCDF_Psi4,
103  'RDVR3_PSI4':RDVR3_Psi4,
104  'INTERACTION_AMBER':Interaction_AMBER,
105  'INTERACTION_GMX':Interaction_GMX,
106  'INTERACTION_TINKER':Interaction_TINKER,
107  'INTERACTION_OPENMM':Interaction_OpenMM,
108  'BINDINGENERGY_TINKER':BindingEnergy_TINKER,
109  'BINDINGENERGY_GMX':BindingEnergy_GMX,
110  'BINDINGENERGY_OPENMM':BindingEnergy_OpenMM,
111  'MOMENTS_TINKER':Moments_TINKER,
112  'MOMENTS_GMX':Moments_GMX,
113  'MOMENTS_OPENMM':Moments_OpenMM,
114  'HYDRATION_OPENMM':Hydration_OpenMM,
115  'OPTGEO_OPENMM': OptGeoTarget_OpenMM,
116  'OPTGEO_SMIRNOFF': OptGeoTarget_SMIRNOFF,
117  'OPTGEOTARGET_OPENMM': OptGeoTarget_OpenMM, # LPW: In the future, the user interface should not include the word 'target' in the target name.
118  'OPTGEOTARGET_SMIRNOFF': OptGeoTarget_SMIRNOFF, # Keeping these two for compatibility with released FB calculation files.
119  'TORSIONPROFILE_OPENMM': TorsionProfileTarget_OpenMM,
120  'TORSIONPROFILE_SMIRNOFF': TorsionProfileTarget_SMIRNOFF,
121  'EVALUATOR_SMIRNOFF': Evaluator_SMIRNOFF,
122  'REMOTE_TARGET':RemoteTarget,
123  }
124 
125 
126 
127 Letters = ['X','G','H']
129 class Objective(forcebalance.BaseClass):
130  """ Objective function.
131 
132  The objective function is a combination of contributions from the different
133  fitting targets. Basically, it loops through the targets, gets their
134  contributions to the objective function and then sums all of them
135  (although more elaborate schemes are conceivable). The return value is the
136  same data type as calling the target itself: a dictionary containing
137  the objective function, the gradient and the Hessian.
138 
139  The penalty function is also computed here; it keeps the parameters from straying
140  too far from their initial values.
141 
142  @param[in] mvals The mathematical parameters that enter into computing the objective function
143  @param[in] Order The requested order of differentiation
144  """
145  def __init__(self, options, tgt_opts, forcefield):
147  super(Objective, self).__init__(options)
148  self.set_option(options, 'penalty_type')
149  self.set_option(options, 'penalty_additive')
150  self.set_option(options, 'penalty_multiplicative')
151  self.set_option(options, 'penalty_hyperbolic_b')
152  self.set_option(options, 'penalty_alpha')
153  self.set_option(options, 'penalty_power')
154  self.set_option(options, 'normalize_weights')
155 
156  self.set_option(options, 'wq_port')
157 
158  self.set_option(options, 'asynchronous')
159 
160 
161  self.Targets = []
162  enable_smirnoff_prints = False # extra prints for SMIRNOFF forcefield
163  for opts in tgt_opts:
164  if opts['type'] not in Implemented_Targets:
165  logger.error('The target type \x1b[1;91m%s\x1b[0m is not implemented!\n' % opts['type'])
166  raise RuntimeError
167  elif opts['type'].endswith("SMIRNOFF"):
168  enable_smirnoff_prints = True
169  # Create a target object. This is done by looking up the
170  # Target class from the Implemented_Targets dictionary
171  # using opts['type'] as the key. The object is created by
172  # passing (options, opts, forcefield) to the constructor.
173  if opts["remote"] and self.wq_port != 0: Tgt = forcebalance.target.RemoteTarget(options, opts, forcefield)
174  else: Tgt = Implemented_Targets[opts['type']](options,opts,forcefield)
175  self.Targets.append(Tgt)
176  printcool_dictionary(Tgt.PrintOptionDict,"Setup for target %s :" % Tgt.name)
177  if len(set([Tgt.name for Tgt in self.Targets])) != len([Tgt.name for Tgt in self.Targets]):
178  logger.error("The list of target names is not unique!\n")
179  raise RuntimeError
180  if enable_smirnoff_prints:
181  smirnoff_analyze_parameter_coverage(forcefield, tgt_opts)
182 
183  self.FF = forcefield
184 
185  self.Penalty = Penalty(self.penalty_type,forcefield,self.penalty_additive,
186  self.penalty_multiplicative,self.penalty_hyperbolic_b,
187  self.penalty_alpha,self.penalty_power)
188 
189  if self.normalize_weights:
190  self.WTot = np.sum([i.weight for i in self.Targets])
191  else:
192  self.WTot = 1.0
193  self.ObjDict = OrderedDict()
194  self.ObjDict_Last = OrderedDict()
196  # Create the work queue here.
197  if self.wq_port != 0:
198  createWorkQueue(self.wq_port)
199  logger.info('Work Queue is listening on %d\n' % self.wq_port)
200 
201  printcool_dictionary(self.PrintOptionDict, "Setup for objective function :")
202 
203 
204  def Target_Terms(self, mvals, Order=0, verbose=False, customdir=None):
205 
206  Objective = {'X':0.0, 'G':np.zeros(self.FF.np), 'H':np.zeros((self.FF.np,self.FF.np))}
207  # Loop through the targets, stage the directories and submit the Work Queue processes.
208  for Tgt in self.Targets:
209  Tgt.stage(mvals, AGrad = Order >= 1, AHess = Order >= 2, customdir=customdir)
210  if self.asynchronous:
211  # Asynchronous evaluation of objective function and Work Queue tasks.
212  # Create a list of the targets, and remove them from the list as they are finished.
213  Need2Evaluate = self.Targets[:]
214  # This ensures that the OrderedDict doesn't get out of order.
215  for Tgt in self.Targets:
216  self.ObjDict[Tgt.name] = None
217  # Loop through the targets and compute the objective function for ones that are finished.
218  while len(Need2Evaluate) > 0:
219  for Tgt in Need2Evaluate:
220  if Tgt.wq_complete():
221  # List of functions that I can call.
222  Funcs = [Tgt.get_X, Tgt.get_G, Tgt.get_H]
223  # Call the appropriate function
224  Ans = Funcs[Order](mvals, customdir=customdir)
225  # Print out the qualitative indicators
226  if verbose:
227  Tgt.meta_indicate(customdir=customdir)
228  # Note that no matter which order of function we call, we still increment the objective / gradient / Hessian the same way.
229  if not in_fd():
230  self.ObjDict[Tgt.name] = {'w' : Tgt.weight/self.WTot , 'x' : Ans['X']}
231  for i in range(3):
232  Objective[Letters[i]] += Ans[Letters[i]]*Tgt.weight/self.WTot
233  Need2Evaluate.remove(Tgt)
234  break
235  else:
236  pass
237  else:
238  wq = getWorkQueue()
239  if wq is not None:
240  wq_wait(wq)
241  for Tgt in self.Targets:
242  # The first call is always done at the midpoint.
243  Tgt.bSave = True
244  # List of functions that I can call.
245  Funcs = [Tgt.get_X, Tgt.get_G, Tgt.get_H]
246  # Call the appropriate function
247  Ans = Funcs[Order](mvals, customdir=customdir)
248  # Print out the qualitative indicators
249  if verbose:
250  Tgt.meta_indicate(customdir=customdir)
251  # Note that no matter which order of function we call, we still increment the objective / gradient / Hessian the same way.
252  if not in_fd():
253  self.ObjDict[Tgt.name] = {'w' : Tgt.weight/self.WTot , 'x' : Ans['X']}
254  for i in range(3):
255  Objective[Letters[i]] += Ans[Letters[i]]*Tgt.weight/self.WTot
256  # The target has evaluated at least once.
257  for Tgt in self.Targets:
258  Tgt.evaluated = True
259  # Safeguard to make sure we don't have exact zeros on Hessian diagonal
260  for i in range(self.FF.np):
261  if Objective['H'][i,i] == 0.0:
262  Objective['H'][i,i] = 1.0
263  return Objective
264 
265  def Indicate(self):
266  """ Print objective function contributions. """
267  PrintDict = OrderedDict()
268  Total = 0.0
269  Change = False
270  color = "\x1b[0m"
271  for key, val in self.ObjDict.items():
272  if key == 'Total' : continue
273  color = "\x1b[94m"
274  if key in self.ObjDict_Last:
275  Change = True
276  # print(self.ObjDict[key], self.ObjDict_Last[key])
277  if self.ObjDict[key]['x'] <= self.ObjDict_Last[key]['x']:
278  color = "\x1b[92m"
279  elif self.ObjDict[key]['x'] > self.ObjDict_Last[key]['x']:
280  color = "\x1b[91m"
281  PrintDict[key] = "% 12.5f % 10.3f %s% 16.5e%s" % (val['x'],val['w'],color,val['x']*val['w'],"\x1b[0m")
282  if Change:
283  xnew = self.ObjDict[key]['x'] * self.ObjDict[key]['w']
284  xold = self.ObjDict_Last[key]['x'] * self.ObjDict_Last[key]['w']
285  PrintDict[key] += " ( %+10.3e )" % (xnew - xold)
286  Total += val['x']*val['w']
287  self.ObjDict['Total'] = Total
288  if 'Total' in self.ObjDict_Last:
289  Change = True
290  if self.ObjDict['Total'] <= self.ObjDict_Last['Total']:
291  color = "\x1b[92m"
292  elif self.ObjDict['Total'] > self.ObjDict_Last['Total']:
293  color = "\x1b[91m"
294  PrintDict['Total'] = "% 12s % 10s %s% 16.5e%s" % ("","",color,Total,"\x1b[0m")
295  if Change:
296  xnew = self.ObjDict['Total']
297  xold = self.ObjDict_Last['Total']
298  PrintDict['Total'] += " ( %+10.3e )" % (xnew - xold)
299  Title = "Objective Function Breakdown\n %-20s %55s" % ("Target Name", "Residual x Weight = Contribution (Current-Prev)")
300  else:
301  Title = "Objective Function Breakdown\n %-20s %40s" % ("Target Name", "Residual x Weight = Contribution")
302  printcool_dictionary(PrintDict,color=4,title=Title)
303  return
304 
305  def Full(self, vals, Order=0, verbose=False, customdir=None):
306  Objective = self.Target_Terms(vals, Order, verbose, customdir)
307 
308  if self.FF.use_pvals:
309  Extra = self.Penalty.compute(self.FF.create_mvals(vals),Objective)
310  else:
311  Extra = self.Penalty.compute(vals,Objective)
312  Objective['X0'] = Objective['X']
313  Objective['G0'] = Objective['G'].copy()
314  Objective['H0'] = Objective['H'].copy()
315  if not in_fd():
316  self.ObjDict['Regularization'] = {'w' : 1.0, 'x' : Extra[0]}
317  if verbose:
318  self.Indicate()
319  for i in range(3):
320  Objective[Letters[i]] += Extra[i]
321  return Objective
322 
323 class Penalty(object):
324  """ Penalty functions for regularizing the force field optimizer.
325 
326  The purpose for this module is to improve the behavior of our optimizer;
327  essentially, our problem is fraught with 'linear dependencies', a.k.a.
328  directions in the parameter space that the objective function does not
329  respond to. This would happen if a parameter is just plain useless, or
330  if there are two or more parameters that describe the same thing.
331 
332  To accomplish these objectives, a penalty function is added to the
333  objective function. Generally, the more the parameters change (i.e.
334  the greater the norm of the parameter vector), the greater the
335  penalty. Note that this is added on after all of the other
336  contributions have been computed. This only matters if the penalty
337  'multiplies' the objective function: Obj + Obj*Penalty, but we also
338  have the option of an additive penalty: Obj + Penalty.
339 
340  Statistically, this is called regularization. If the penalty function
341  is the norm squared of the parameter vector, it is called ridge regression.
342  There is also the option of using simply the norm, and this is called lasso,
343  but I think it presents problems for the optimizer that I need to work out.
344 
345  Note that the penalty functions can be considered as part of a 'maximum
346  likelihood' framework in which we assume a PRIOR PROBABILITY of the
347  force field parameters around their initial values. The penalty function
348  is related to the prior by an exponential. Ridge regression corresponds
349  to a Gaussian prior and lasso corresponds to an exponential prior. There
350  is also 'elastic net regression' which interpolates between Gaussian
351  and exponential using a tuning parameter.
352 
353  Our priors are adjustable too - there is one parameter, which is the width
354  of the distribution. We can even use a noninformative prior for the
355  distribution widths (hyperprior!). These are all important things to
356  consider later.
357 
358  Importantly, note that here there is no code that treats the distribution
359  width. That is because the distribution width is wrapped up in the
360  rescaling factors, which is essentially a coordinate transformation
361  on the parameter space. More documentation on this will follow, perhaps
362  in the 'rsmake' method.
363 
364  """
365  Pen_Names = {'HYP' : 1, 'HYPER' : 1, 'HYPERBOLIC' : 1, 'L1' : 1, 'HYPERBOLA' : 1,
366  'PARA' : 2, 'PARABOLA' : 2, 'PARABOLIC' : 2, 'L2': 2, 'QUADRATIC' : 2,
367  'BOX' : 3, 'FUSE' : 4, 'FUSE-L0' : 5, 'FUSE-BARRIER' : 6}
369  def __init__(self, User_Option, ForceField, Factor_Add=0.0, Factor_Mult=0.0, Factor_B=0.1, Alpha=1.0, Power=2.0):
370  self.fadd = Factor_Add
371  self.fmul = Factor_Mult
372  self.a = Alpha
373  self.b = Factor_B
374  self.p = Power
375  self.FF = ForceField
376  self.ptyp = self.Pen_Names[User_Option.upper()]
377  self.Pen_Tab = {1 : self.HYP, 2: self.L2_norm, 3: self.BOX, 4: self.FUSE, 5:self.FUSE_L0, 6: self.FUSE_BARRIER}
378  if User_Option.upper() == 'L1':
379  logger.info("L1 norm uses the hyperbolic penalty, make sure penalty_hyperbolic_b is set sufficiently small\n")
380  elif self.ptyp == 1:
381  logger.info("Using hyperbolic regularization (Laplacian prior) with strength %.1e (+), %.1e (x) and tightness %.1e\n" % (Factor_Add, Factor_Mult, Factor_B))
382  elif self.ptyp == 2:
383  if Power == 2.0:
384  logger.info("Using parabolic regularization (Gaussian prior) with strength %.1e (+), %.1e (x)\n" % (Factor_Add, Factor_Mult))
385  elif Power > 2.0:
386  logger.info("Using customized L2-regularization with exponent %.1f, strength %.1e (+), %.1e (x)\n" % (Power, Factor_Add, Factor_Mult))
387  else:
388  logger.error("In L2-regularization, penalty_power must be >= 2.0 (currently %.1f)\n" % (Power))
389  raise RuntimeError
390  elif self.ptyp == 3:
391  if Power == 2.0:
392  logger.info("Using box-style regularization with exponent %.1f, strength %.1e (+), %.1e (x): same as L2\n" % (Power, Factor_Add, Factor_Mult))
393  elif Power > 2.0:
394  logger.info("Using box-style regularization with exponent %.1f, strength %.1e (+), %.1e (x)\n" % (Power, Factor_Add, Factor_Mult))
395  else:
396  logger.error("In box-style regularization, penalty_power must be >= 2.0 (currently %.1f)\n" % (Power))
397  raise RuntimeError
398  elif self.ptyp == 4:
399  logger.info("Using L1 Fusion Penalty (only relevant for basis set optimizations at the moment) with strength %.1e\n" % Factor_Add)
400  elif self.ptyp == 5:
401  logger.info("Using L0-L1 Fusion Penalty (only relevant for basis set optimizations at the moment) with strength %.1e and switching distance %.1e\n" % (Factor_Add, Alpha))
402  elif self.ptyp == 6:
403  logger.info("Using L1 Fusion Penalty with Log Barrier (only relevant for basis set optimizations at the moment) with strength %.1e and barrier distance %.1e\n" % (Factor_Add, Alpha))
404  if self.ptyp not in (2, 3) and Power != 2.0:
405  logger.error("Custom power %.2f is only supported with L2 or box-style regularization (penalty_type L2 or box)\n" % Power)
406  raise RuntimeError
407 
408 
409  if self.ptyp in [4,5,6]:
410  self.spacings = self.FF.find_spacings()
411  printcool_dictionary(self.spacings, title="Starting zeta spacings\n(Pay attention to these)")
412 
413  def compute(self, mvals, Objective):
414  K0, K1, K2 = self.Pen_Tab[self.ptyp](mvals)
415  if self.fadd > 0.0:
416  XAdd = K0 * self.fadd
417  GAdd = K1 * self.fadd
418  HAdd = K2 * self.fadd
419  else:
420  NP = len(mvals)
421  XAdd = 0.0
422  GAdd = np.zeros(NP)
423  HAdd = np.zeros((NP, NP))
424  if self.fmul > 0.0:
425  X = Objective['X']
426  G = Objective['G']
427  H = Objective['H']
428  XAdd += ( X*K0 ) * self.fmul
429  GAdd += np.array( G*K0 + X*K1 ) * self.fmul
430  GK1 = np.reshape(G, (1, -1))*np.reshape(K1, (-1, 1))
431  K1G = np.reshape(K1, (1, -1))*np.reshape(G, (-1, 1))
432  HAdd += np.array( H*K0+GK1+K1G+X*K2 ) * self.fmul
433  return XAdd, GAdd, HAdd
434 
435  def L2_norm(self, mvals):
436  """
437  Harmonic L2-norm constraints. These are the ones that I use
438  the most often to regularize my optimization.
439 
440  @param[in] mvals The parameter vector
441  @return DC0 The norm squared of the vector
442  @return DC1 The gradient of DC0
443  @return DC2 The Hessian (just a constant)
444 
445  """
446  if self.p == 2.0:
447  mvals = np.array(mvals)
448  DC0 = np.dot(mvals, mvals)
449  DC1 = 2*np.array(mvals)
450  DC2 = 2*np.eye(len(mvals))
451  else:
452  mvals = np.array(mvals)
453  m2 = np.dot(mvals, mvals)
454  p = float(self.p)
455  DC0 = m2**(p/2)
456  DC1 = p*(m2**(p/2-1))*mvals
457  DC2 = p*(m2**(p/2-1))*np.eye(len(mvals))
458  DC2 += p*(p-2)*(m2**(p/2-2))*np.outer(mvals, mvals)
459  return DC0, DC1, DC2
460 
461  def BOX(self, mvals):
462  """
463  Box-style constraints. A penalty term of mvals[i]^Power is added for each parameter.
464 
465  If Power = 2.0 (default value of penalty_power) then this is the same as L2 regularization.
466  If set to a larger number such as 12.0, then this corresponds to adding a flat-bottomed
467  restraint to each parameter separately.
468 
469  @param[in] mvals The parameter vector
470  @return DC0 The norm squared of the vector
471  @return DC1 The gradient of DC0
472  @return DC2 The Hessian (just a constant)
473  """
474 
475  if self.p == 2.0:
476  return self.L2_norm(mvals)
477  else:
478  mvals = np.array(mvals)
479  p = float(self.p)
480  DC0 = np.sum(mvals**self.p)
481  DC1 = self.p*(mvals**(self.p-1))
482  DC2 = np.diag(self.p*(self.p-1)*(mvals**(self.p-2)))
483  return DC0, DC1, DC2
484 
485  def HYP(self, mvals):
486  """
487  Hyperbolic constraints. Depending on the 'b' parameter, the smaller it is,
488  the closer we are to an L1-norm constraint. If we use these, we expect
489  a properly-behaving optimizer to make several of the parameters very nearly zero
490  (which would be cool).
491 
492  @param[in] mvals The parameter vector
493  @return DC0 The hyperbolic penalty
494  @return DC1 The gradient
495  @return DC2 The Hessian
496 
497  """
498  mvals = np.array(mvals)
499  NP = len(mvals)
500  sqt = (mvals**2 + self.b**2)**0.5
501  DC0 = np.sum(sqt - self.b)
502  DC1 = mvals*(1.0/sqt)
503  DC2 = np.diag(self.b**2*(1.0/sqt**3))
505  return DC0, DC1, DC2
506 
507  def FUSE(self, mvals):
508  Groups = defaultdict(list)
509  for p, pid in enumerate(self.FF.plist):
510  if 'Exponent' not in pid or len(pid.split()) != 1:
511  warn_press_key("Fusion penalty currently implemented only for basis set optimizations, where parameters are like this: Exponent:Elem=H,AMom=D,Bas=0,Con=0")
512  Data = dict([(i.split('=')[0],i.split('=')[1]) for i in pid.split(':')[1].split(',')])
513  if 'Con' not in Data or Data['Con'] != '0':
514  warn_press_key("More than one contraction coefficient found! You should expect the unexpected")
515  key = Data['Elem']+'_'+Data['AMom']
516  Groups[key].append(p)
517  pvals = self.FF.create_pvals(mvals)
518  DC0 = 0.0
519  DC1 = np.zeros(self.FF.np)
520  DC2 = np.zeros(self.FF.np)
521  for gnm, pidx in Groups.items():
522  # The group of parameters for a particular element / angular momentum.
523  pvals_grp = pvals[pidx]
524  # The order that the parameters come in.
525  Order = np.argsort(pvals_grp)
526  # The number of nearest neighbor pairs.
527  #print Order
528  for p in range(len(Order) - 1):
529  # The pointers to the parameter indices.
530  pi = pidx[Order[p]]
531  pj = pidx[Order[p+1]]
532  # pvals[pi] is the SMALLER parameter.
533  # pvals[pj] is the LARGER parameter.
534  dp = np.log(pvals[pj]) - np.log(pvals[pi])
535  # dp = (np.log(pvals[pj]) - np.log(pvals[pi])) / self.spacings[gnm]
536  DC0 += (dp**2 + self.b**2)**0.5 - self.b
537  DC1[pi] -= dp*(dp**2 + self.b**2)**-0.5
538  DC1[pj] += dp*(dp**2 + self.b**2)**-0.5
539  # The second derivatives have off-diagonal terms,
540  # but we're not using them right now anyway
541  # I will implement them if necessary.
542  # DC2[pi] -= self.b**2*(dp**2 + self.b**2)**-1.5
543  # DC2[pj] += self.b**2*(dp**2 + self.b**2)**-1.5
544  #print "pvals[%i] = %.4f, pvals[%i] = %.4f dp = %.4f" % (pi, pvals[pi], pj, pvals[pj], dp),
545  #print "First Derivative = % .4f, Second Derivative = % .4f" % (dp*(dp**2 + self.b**2)**-0.5, self.b**2*(dp**2 + self.b**2)**-1.5)
546  return DC0, DC1, np.diag(DC2)
547 
548  def FUSE_BARRIER(self, mvals):
549  Groups = defaultdict(list)
550  for p, pid in enumerate(self.FF.plist):
551  if 'Exponent' not in pid or len(pid.split()) != 1:
552  warn_press_key("Fusion penalty currently implemented only for basis set optimizations, where parameters are like this: Exponent:Elem=H,AMom=D,Bas=0,Con=0")
553  Data = dict([(i.split('=')[0],i.split('=')[1]) for i in pid.split(':')[1].split(',')])
554  if 'Con' not in Data or Data['Con'] != '0':
555  warn_press_key("More than one contraction coefficient found! You should expect the unexpected")
556  key = Data['Elem']+'_'+Data['AMom']
557  Groups[key].append(p)
558  pvals = self.FF.create_pvals(mvals)
559  DC0 = 0.0
560  DC1 = np.zeros(self.FF.np)
561  DC2 = np.zeros(self.FF.np)
562  for gnm, pidx in Groups.items():
563  # The group of parameters for a particular element / angular momentum.
564  pvals_grp = pvals[pidx]
565  # The order that the parameters come in.
566  Order = np.argsort(pvals_grp)
567  # The number of nearest neighbor pairs.
568  #print Order
569  for p in range(len(Order) - 1):
570  # The pointers to the parameter indices.
571  pi = pidx[Order[p]]
572  pj = pidx[Order[p+1]]
573  # pvals[pi] is the SMALLER parameter.
574  # pvals[pj] is the LARGER parameter.
575  dp = np.log(pvals[pj]) - np.log(pvals[pi])
576  # dp = (np.log(pvals[pj]) - np.log(pvals[pi])) / self.spacings[gnm]
577  DC0 += (dp**2 + self.b**2)**0.5 - self.b - self.a*np.log(dp) + self.a*np.log(self.a)
578  DC1[pi] -= dp*(dp**2 + self.b**2)**-0.5 - self.a/dp
579  DC1[pj] += dp*(dp**2 + self.b**2)**-0.5 - self.a/dp
580  # The second derivatives have off-diagonal terms,
581  # but we're not using them right now anyway
582  # I will implement them later if necessary.
583  # DC2[pi] -= self.b**2*(dp**2 + self.b**2)**-1.5 - self.a/dp**2
584  # DC2[pj] += self.b**2*(dp**2 + self.b**2)**-1.5 - self.a/dp**2
585  #print "pvals[%i] = %.4f, pvals[%i] = %.4f dp = %.4f" % (pi, pvals[pi], pj, pvals[pj], dp),
586  #print "First Derivative = % .4f, Second Derivative = % .4f" % (dp*(dp**2 + self.b**2)**-0.5, self.b**2*(dp**2 + self.b**2)**-1.5)
587  return DC0, DC1, np.diag(DC2)
588 
589 
590  def FUSE_L0(self, mvals):
591  Groups = defaultdict(list)
592  for p, pid in enumerate(self.FF.plist):
593  if 'Exponent' not in pid or len(pid.split()) != 1:
594  warn_press_key("Fusion penalty currently implemented only for basis set optimizations, where parameters are like this: Exponent:Elem=H,AMom=D,Bas=0,Con=0")
595  Data = dict([(i.split('=')[0],i.split('=')[1]) for i in pid.split(':')[1].split(',')])
596  if 'Con' not in Data or Data['Con'] != '0':
597  warn_press_key("More than one contraction coefficient found! You should expect the unexpected")
598  key = Data['Elem']+'_'+Data['AMom']
599  Groups[key].append(p)
600  pvals = self.FF.create_pvals(mvals)
601  #print "pvals: ", pvals
602  DC0 = 0.0
603  DC1 = np.zeros(self.FF.np)
604  DC2 = np.zeros((self.FF.np,self.FF.np))
605  for gnm, pidx in Groups.items():
606  # The group of parameters for a particular element / angular momentum.
607  pvals_grp = pvals[pidx]
608  # The order that the parameters come in.
609  Order = np.argsort(pvals_grp)
610  # The number of nearest neighbor pairs.
611  #print Order
612  Contribs = []
613  dps = []
614  for p in range(len(Order) - 1):
615  # The pointers to the parameter indices.
616  pi = pidx[Order[p]]
617  pj = pidx[Order[p+1]]
618  # pvals[pi] is the SMALLER parameter.
619  # pvals[pj] is the LARGER parameter.
620  dp = np.log(pvals[pj]) - np.log(pvals[pi])
621  # dp = (np.log(pvals[pj]) - np.log(pvals[pi])) / self.spacings[gnm]
622  dp2b2 = dp**2 + self.b**2
623  h = self.a*((dp2b2)**0.5 - self.b)
624  hp = self.a*(dp*(dp2b2)**-0.5)
625  hpp = self.a*(self.b**2*(dp2b2)**-1.5)
626  emh = np.exp(-1*h)
627  dps.append(dp)
628  Contribs.append((1.0 - emh))
629  DC0 += (1.0 - emh)
630  DC1[pi] -= hp*emh
631  DC1[pj] += hp*emh
632  # for i in self.FF.redirect:
633  # p = mvals[i]
634  # DC0 += 1e-6*p*p
635  # DC1[i] = 2e-6*p
636 
637  # The second derivatives have off-diagonal terms,
638  # but we're not using them right now anyway
639  #DC2[pi,pi] += (hpp - hp**2)*emh
640  #DC2[pi,pj] -= (hpp - hp**2)*emh
641  #DC2[pj,pi] -= (hpp - hp**2)*emh
642  #DC2[pj,pj] += (hpp - hp**2)*emh
643  #print "pvals[%i] = %.4f, pvals[%i] = %.4f dp = %.4f" % (pi, pvals[pi], pj, pvals[pj], dp),
644  #print "First Derivative = % .4f, Second Derivative = % .4f" % (dp*(dp**2 + self.b**2)**-0.5, self.b**2*(dp**2 + self.b**2)**-1.5)
645 
646  #print "grp:", gnm, "dp:", ' '.join(["% .1e" % i for i in dps]), "Contributions:", ' '.join(["% .1e" % i for i in Contribs])
647 
648  #print DC0, DC1, DC2
649  #print pvals
650  #raw_input()
651 
652  return DC0, DC1, DC2
653 
654  #return self.HYP(mvals)
def FUSE(self, mvals)
Definition: objective.py:514
WTot
Obtain the denominator.
Definition: objective.py:192
def HYP(self, mvals)
Hyperbolic constraints.
Definition: objective.py:504
def compute(self, mvals, Objective)
Definition: objective.py:417
Objective function.
Definition: objective.py:146
Nifty functions, intended to be imported by any module within ForceBalance.
def smirnoff_analyze_parameter_coverage(forcefield, tgt_opts)
Definition: smirnoffio.py:66
def Full(self, vals, Order=0, verbose=False, customdir=None)
Definition: objective.py:308
def BOX(self, mvals)
Box-style constraints.
Definition: objective.py:479
def createWorkQueue(wq_port, debug=True, name=package)
Definition: nifty.py:912
Penalty functions for regularizing the force field optimizer.
Definition: objective.py:368
Match an empirical potential to the counterpoise correction for basis set superposition error (BSSE)...
Penalty
Initialize the penalty function.
Definition: objective.py:187
def in_fd()
Invoking this function from anywhere will tell us whether we&#39;re being called by a finite-difference f...
GROMACS input/output.
TINKER input/output.
dictionary Pen_Names
Definition: objective.py:369
def FUSE_L0(self, mvals)
Definition: objective.py:597
def L2_norm(self, mvals)
Harmonic L2-norm constraints.
Definition: objective.py:450
def FUSE_BARRIER(self, mvals)
Definition: objective.py:555
def wq_wait(wq, wait_time=10, wait_intvl=10, print_time=60, verbose=False)
This function waits until the work queue is completely empty.
Definition: nifty.py:1056
FF
The force field (it seems to be everywhere)
Definition: objective.py:185
def __init__(self, options, tgt_opts, forcefield)
Definition: objective.py:147
def Indicate(self)
Print objective function contributions.
Definition: objective.py:269
def Target_Terms(self, mvals, Order=0, verbose=False, customdir=None)
Definition: objective.py:206
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
AMBER force field input/output.
spacings
Find exponential spacings.
Definition: objective.py:414
Internal implementation of energy matching (for TIP3P water only)
Targets
Work Queue Port (The specific target itself may or may not actually use this.)
Definition: objective.py:163
def __init__(self, User_Option, ForceField, Factor_Add=0.0, Factor_Mult=0.0, Factor_B=0.1, Alpha=1.0, Power=2.0)
Definition: objective.py:373
def getWorkQueue()
Definition: nifty.py:904
OpenMM input/output.
PSI4 force field input/output.