ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
abinitio.py
Go to the documentation of this file.
1 """ @package forcebalance.abinitio Ab-initio fitting module (energies, forces, resp).
2 
3 @author Lee-Ping Wang
4 @date 05/2012
5 """
6 from __future__ import division
7 from __future__ import print_function
8 
9 from builtins import zip
10 from builtins import range
11 import os
12 import shutil
13 from forcebalance.nifty import col, eqcgmx, flat, floatornan, fqcgmx, invert_svd, kb, printcool, bohr2ang, warn_press_key, warn_once, pvec1d, commadash, uncommadash, isint
14 import numpy as np
15 from forcebalance.target import Target
16 from forcebalance.molecule import Molecule, format_xyz_coord
17 from re import match, sub
18 import subprocess
19 from subprocess import PIPE
20 from forcebalance.finite_difference import fdwrap, f1d2p, f12d3p, in_fd
21 from collections import defaultdict, OrderedDict
22 import itertools
23 #from IPython import embed
24 #from _increment import AbInitio_Build
25 
26 from forcebalance.output import getLogger
27 logger = getLogger(__name__)
28 
29 def norm2(arr, a=0, n=None, step=3):
30  """
31  Given a one-dimensional array, return the norm-squared of
32  every "step" elements, starting at 'a' and computing 'n' total
33  elements (so arr[a:a+step*n] must be valid).
34 
35  Parameters
36  ----------
37  arr : np.ndarray
38  One-dimensional array to be normed
39  a : int, default=0
40  The starting index
41  n : int, or None
42  The number of norms to calculate (in intervals of step)
43  step : int, default=3
44  The number of elements in each norm calculation (this is usually 3)
45  """
46  if len(arr.shape) != 1:
47  raise RuntimeError("Please only pass a one-dimensional array")
48  if n is not None:
49  if arr.shape[0] < a+step*n:
50  raise RuntimeError("Please provide an array of length >= %i" % (a+step*n))
51  else:
52  if ((arr.shape[0]-a)%step != 0):
53  raise RuntimeError("Please provide an array with (length-%i) divisible by %i" % (a, step))
54  n = int((arr.shape[0]-a)/step)
55  answer = []
56  for j in range(n):
57  d = arr[a+step*j:a+step*j+step]
58  answer.append(np.dot(d,d))
59  return np.array(answer)
60 
61 class AbInitio(Target):
62 
63  """ Subclass of Target for fitting force fields to ab initio data.
64 
65  Currently Gromacs-X2, Gromacs, Tinker, OpenMM and AMBER are supported.
66 
67  We introduce the following concepts:
68  - The number of snapshots
69  - The reference energies and forces (eqm, fqm) and the file they belong in (qdata.txt)
70  - The sampling simulation energies (emd0)
71  - The WHAM Boltzmann weights (these are computed externally and passed in)
72  - The QM Boltzmann weights (computed internally using the difference between eqm and emd0)
73 
74  There are also these little details:
75  - Switches for whether to turn on certain Boltzmann weights (they stack)
76  - Temperature for the QM Boltzmann weights
77  - Whether to fit a subset of atoms
78 
79  This subclass contains the 'get' method for building the objective
80  function from any simulation software (a driver to run the program and
81  read output is still required). The 'get' method can be overridden
82  by subclasses like AbInitio_GMX."""
83 
84  def __init__(self,options,tgt_opts,forcefield):
85  """
86  Initialization; define a few core concepts.
87 
88  @todo Obtain the number of true atoms (or the particle -> atom mapping)
89  from the force field.
90  """
91 
92 
93  super(AbInitio,self).__init__(options,tgt_opts,forcefield)
94 
95  #======================================#
96  # Options that are given by the parser #
97  #======================================#
98 
99 
100  self.set_option(tgt_opts,'shots','ns')
101 
102  self.set_option(tgt_opts,'absolute','absolute')
103 
104  self.set_option(tgt_opts,'fitatoms','fitatoms_in')
105 
106  self.set_option(tgt_opts,'energy','energy')
107 
108  self.set_option(tgt_opts,'force','force')
109 
110  self.set_option(tgt_opts,'resp','resp')
111  self.set_option(tgt_opts,'resp_a','resp_a')
112  self.set_option(tgt_opts,'resp_b','resp_b')
113 
114  self.set_option(tgt_opts,'w_energy','w_energy')
115  self.set_option(tgt_opts,'w_force','w_force')
116  if not self.force:
117  self.w_force = 0.0
118  self.set_option(tgt_opts,'energy_rms_override','energy_rms_override')
119  self.set_option(tgt_opts,'force_rms_override','force_rms_override')
120  self.set_option(tgt_opts,'force_map','force_map')
121  self.set_option(tgt_opts,'w_netforce','w_netforce')
122  self.set_option(tgt_opts,'w_torque','w_torque')
123  self.set_option(tgt_opts,'w_resp','w_resp')
124  # Normalize the contributions to the objective function
125  self.set_option(tgt_opts,'w_normalize')
126 
127  self.set_option(tgt_opts,'writelevel','writelevel')
128 
130  self.set_option(tgt_opts,'all_at_once','all_at_once')
131 
132  self.set_option(tgt_opts,'run_internal','run_internal')
133 
134  self.set_option(options,'have_vsite','have_vsite')
135 
136  self.set_option(tgt_opts,'attenuate','attenuate')
137 
138  self.set_option(tgt_opts,'energy_denom','energy_denom')
139 
140  self.set_option(tgt_opts,'energy_upper','energy_upper')
141 
142  self.set_option(tgt_opts,'energy_asymmetry')
143  self.savg = (self.energy_asymmetry == 1.0 and not self.absolute)
144  self.asym = (self.energy_asymmetry != 1.0)
145  if self.asym:
146  if not self.all_at_once:
147  logger.error("Asymmetric weights only work when all_at_once is enabled")
148  raise RuntimeError
149  # allow user to overwrite the force denominator
150  self.set_option(tgt_opts,'force_denom','force_denom')
151  #======================================#
152  # Variables which are set here #
153  #======================================#
154 
156  self.loop_over_snapshots = True
157 
158  self.boltz_wts = []
159 
160  self.eqm = []
161 
162  self.fqm = []
163 
164  self.espxyz = []
165 
166  self.espval = []
167 
168  self.qfnm = os.path.join(self.tgtdir,"qdata.txt")
169 
170  self.e_err = 0.0
171  self.e_err_pct = None
172 
173  self.f_err = 0.0
174  self.f_err_pct = 0.0
175 
176  self.esp_err = 0.0
177  self.nf_err = 0.0
178  self.nf_err_pct = 0.0
179  self.tq_err_pct = 0.0
180 
181  self.use_nft = self.w_netforce > 0 or self.w_torque > 0
182 
183  if hasattr(self, 'pdb') and self.pdb is not None:
184  self.mol = Molecule(os.path.join(self.root,self.tgtdir,self.coords),
185  top=(os.path.join(self.root,self.tgtdir,self.pdb)))
186  else:
187  self.mol = Molecule(os.path.join(self.root,self.tgtdir,self.coords))
188 
189  if self.ns != -1:
190  self.mol = self.mol[:self.ns]
191  self.ns = len(self.mol)
192 
193  self.nparticles = len(self.mol.elem)
194 
195  engine_args = OrderedDict(list(self.OptionDict.items()) + list(options.items()))
196  engine_args.pop('name', None)
197 
198  self.engine = self.engine_(target=self, mol=self.mol, **engine_args)
199 
200  self.AtomLists = self.engine.AtomLists
201  self.AtomMask = self.engine.AtomMask
202 
203  self.read_reference_data()
204 
206  self.buildx = True
207 
208  self.save_vmvals = {}
209  self.set_option(None, 'shots', val=self.ns)
210  self.M_orig = None
211 
212  def build_invdist(self, mvals):
213  for i in self.pgrad:
214  if 'VSITE' in self.FF.plist[i]:
215  if i in self.save_vmvals and mvals[i] != self.save_vmvals[i]:
216  self.buildx = True
217  break
218  if not self.buildx: return self.invdists
219  if any(['VSITE' in i for i in self.FF.map.keys()]) or self.have_vsite:
220  logger.info("\rGenerating virtual site positions.%s" % (" "*30))
221  pvals = self.FF.make(mvals)
222  self.mol.xyzs = self.engine.generate_positions()
223  # prepare the distance matrix for esp computations
224  if len(self.espxyz) > 0:
225  invdists = []
226  logger.info("\rPreparing the distance matrix... it will have %i * %i * %i = %i elements" % (self.ns, self.nesp, self.nparticles, self.ns * self.nesp * self.nparticles))
227  sn = 0
228  for espset, xyz in zip(self.espxyz, self.mol.xyzs):
229  logger.info("\rGenerating ESP distances for snapshot %i%s\r" % (sn, " "*50))
230  esparr = np.array(espset).reshape(-1,3)
231  # Create a matrix with Nesp rows and Natoms columns.
232  DistMat = np.array([[np.linalg.norm(i - j) for j in xyz] for i in esparr])
233  invdists.append(1. / (DistMat / bohr2ang))
234  sn += 1
235  for i in self.pgrad:
236  if 'VSITE' in self.FF.plist[i]:
237  self.save_vmvals[i] = mvals[i]
238  self.buildx = False
239  return np.array(invdists)
240 
241  def compute_netforce_torque(self, xyz, force, QM=False):
242  # Convert an array of (3 * n_atoms) atomistic forces
243  # to an array of (3 * (n_forces + n_torques)) net forces and torques.
244  # This code is rather slow. It requires the system to have a list
245  # of masses and blocking numbers.
246 
247  kwds = {"MoleculeNumber" : "molecule",
248  "ResidueNumber" : "residue",
249  "ChargeGroupNumber" : "chargegroup"}
250  if self.force_map == 'molecule' and 'MoleculeNumber' in self.AtomLists:
251  Block = self.AtomLists['MoleculeNumber']
252  elif self.force_map == 'residue' and 'ResidueNumber' in self.AtomLists:
253  Block = self.AtomLists['ResidueNumber']
254  elif self.force_map == 'chargegroup' and 'ChargeGroupNumber' in self.AtomLists:
255  Block = self.AtomLists['ChargeGroupNumber']
256  else:
257  logger.error('force_map keyword "%s" is invalid. Please choose from: %s\n' % (self.force_map, ', '.join(['"%s"' % kwds[k] for k in self.AtomLists.keys() if k in kwds])))
258  raise RuntimeError
259 
260  nft = len(self.fitatoms)
261  # Number of particles that the force is acting on
262  nfp = force.reshape(-1,3).shape[0]
263  # Number of particles in the XYZ coordinates
264  nxp = xyz.shape[0]
265  # Number of particles in self.AtomLists
266  npr = len(self.AtomMask)
267  # Number of true atoms
268  nat = sum(self.AtomMask)
269 
270  mask = np.array([i for i in range(npr) if self.AtomMask[i]])
271 
272  if nfp not in [npr, nat]:
273  logger.error('Force contains %i particles but expected %i or %i\n' % (nfp, npr, nat))
274  raise RuntimeError
275  elif nfp == nat:
276  frc1 = force.reshape(-1,3)[:nft].flatten()
277  elif nfp == npr:
278  frc1 = force.reshape(-1,3)[mask][:nft].flatten()
279 
280  if nxp not in [npr, nat]:
281  logger.error('Coordinates contains %i particles but expected %i or %i\n' % (nfp, npr, nat))
282  raise RuntimeError
283  elif nxp == nat:
284  xyz1 = xyz[:nft]
285  elif nxp == npr:
286  xyz1 = xyz[mask][:nft]
287 
288  Block = list(np.array(Block)[mask])[:nft]
289  Mass = np.array(self.AtomLists['Mass'])[mask][:nft]
290 
291  NetForces = []
292  Torques = []
293  for b in sorted(set(Block)):
294  AtomBlock = np.array([i for i in range(len(Block)) if Block[i] == b])
295  CrdBlock = np.array(list(itertools.chain(*[list(range(3*i, 3*i+3)) for i in AtomBlock])))
296  com = np.sum(xyz1[AtomBlock]*np.outer(Mass[AtomBlock],np.ones(3)), axis=0) / np.sum(Mass[AtomBlock])
297  frc = frc1[CrdBlock].reshape(-1,3)
298  NetForce = np.sum(frc, axis=0)
299  xyzb = xyz1[AtomBlock]
300  Torque = np.zeros(3)
301  for a in range(len(xyzb)):
302  R = xyzb[a] - com
303  F = frc[a]
304  # I think the unit of torque is in nm x kJ / nm.
305  Torque += np.cross(R, F) / 10
306  NetForces += [i for i in NetForce]
307  # Increment the torques only if we have more than one atom in our block.
308  if len(xyzb) > 1:
309  Torques += [i for i in Torque]
310  netfrc_torque = np.array(NetForces + Torques)
311  self.nnf = int(len(NetForces)/3)
312  self.ntq = int(len(Torques)/3)
313  return netfrc_torque
314 
317  """ Read the reference ab initio data from a file such as qdata.txt.
318 
319  @todo Add an option for picking any slice out of
320  qdata.txt, helpful for cross-validation
321 
322  After reading in the information from qdata.txt, it is converted
323  into the GROMACS energy units (kind of an arbitrary choice);
324  forces (kind of a misnomer in qdata.txt) are multipled by -1
325  to convert gradients to forces.
326 
327  We typically subtract out the mean energies of all energy arrays
328  because energy/force matching does not account for zero-point
329  energy differences between MM and QM (i.e. energy of electrons
330  in core orbitals).
331 
332  A 'hybrid' ensemble is possible where we use 50% MM and 50% QM
333  weights. Please read more in LPW and Troy Van Voorhis, JCP
334  Vol. 133, Pg. 231101 (2010), doi:10.1063/1.3519043. In the
335  updated version of the code (July 13 2016), this feature is
336  currently not implemented due to disuse, but it is easy to
337  re-implement if desired.
338 
339  Finally, note that using non-Boltzmann weights degrades the
340  statistical information content of the snapshots. This
341  problem will generally become worse if the ensemble to which
342  we're reweighting is dramatically different from the one we're
343  sampling from. We end up with a set of Boltzmann weights like
344  [1e-9, 1e-9, 1.0, 1e-9, 1e-9 ... ] and this is essentially just
345  one snapshot.
346 
347  Here, we have a measure for the information content of our snapshots,
348  which comes easily from the definition of information entropy:
349 
350  S = -1*Sum_i(P_i*log(P_i))
351  InfoContent = exp(-S)
352 
353  With uniform weights, InfoContent is equal to the number of snapshots;
354  with horrible weights, InfoContent is closer to one.
355 
356  """
357  # Parse the qdata.txt file
358  for line in open(os.path.join(self.root,self.qfnm)):
359  sline = line.split()
360  if len(sline) == 0: continue
361  elif sline[0] == 'ENERGY':
362  self.eqm.append(float(sline[1]))
363  elif sline[0] in ['FORCES', 'GRADIENT']:
364  self.fqm.append([float(i) for i in sline[1:]])
365  elif sline[0] == 'ESPXYZ':
366  self.espxyz.append([float(i) for i in sline[1:]])
367  elif sline[0] == 'ESPVAL':
368  self.espval.append([float(i) for i in sline[1:]])
369 
370  # Ensure that all lists are of length self.ns
371  self.eqm = self.eqm[:self.ns]
372  self.fqm = self.fqm[:self.ns]
373  self.espxyz = self.espxyz[:self.ns]
374  self.espval = self.espval[:self.ns]
375 
376  # Turn everything into arrays, convert to kJ/mol, and subtract the mean energy from the energy arrays
377  self.eqm = np.array(self.eqm)
378  self.eqm *= eqcgmx
379  if self.asym:
380  self.eqm -= np.min(self.eqm)
381  self.smin = np.argmin(self.eqm)
382  logger.info("Referencing all energies to the snapshot %i (minimum energy structure in QM)\n" % self.smin)
383  elif self.absolute:
384  logger.info("Fitting absolute energies. Make sure you know what you are doing!\n")
385  else:
386  self.eqm -= np.mean(self.eqm)
387 
388  if len(self.fqm) > 0:
389  self.fqm = np.array(self.fqm)
390  self.fqm *= fqcgmx
391  self.qmatoms = list(range(int(self.fqm.shape[1]/3)))
392  else:
393  logger.info("QM forces are not present, only fitting energies.\n")
394  self.force = 0
395  self.w_force = 0
397  # Here we may choose a subset of atoms to do the force matching.
398  if self.force:
399  # Build a list corresponding to the atom indices where we are fitting the forces.
400  if isint(self.fitatoms_in):
401  if int(self.fitatoms_in) == 0:
402  self.fitatoms = self.qmatoms
403  else:
404  warn_press_key("Provided an integer for fitatoms; will assume this means the first %i atoms" % int(self.fitatoms_in))
405  self.fitatoms = list(range(int(self.fitatoms_in)))
406  else:
407  # If provided a "comma and dash" list, then expand the list.
408  self.fitatoms = uncommadash(self.fitatoms_in)
409 
410  if len(self.fitatoms) > len(self.qmatoms):
411  warn_press_key("There are more fitting atoms than the total number of atoms in the QM calculation (something is probably wrong)")
412  else:
413  if self.w_force > 0:
414  if len(self.fitatoms) == len(self.qmatoms):
415  logger.info("Fitting the forces on all atoms\n")
416  else:
417  logger.info("Fitting the forces on atoms %s\n" % commadash(self.fitatoms))
418  logger.info("Pruning the quantum force matrix...\n")
419  selct = list(itertools.chain(*[[3*i+j for j in range(3)] for i in self.fitatoms]))
420  self.fqm = self.fqm[:, selct]
421  else:
422  self.fitatoms = []
423 
424  self.nesp = len(self.espval[0]) if len(self.espval) > 0 else 0
425 
426  if self.attenuate:
427  # Attenuate energies by an amount proportional to their
428  # value above the minimum.
429  eqm1 = self.eqm - np.min(self.eqm)
430  denom = self.energy_denom * 4.184 # kcal/mol to kJ/mol
431  upper = self.energy_upper * 4.184 # kcal/mol to kJ/mol
432  self.boltz_wts = np.ones(self.ns)
433  for i in range(self.ns):
434  if eqm1[i] > upper:
435  self.boltz_wts[i] = 0.0
436  elif eqm1[i] < denom:
437  self.boltz_wts[i] = 1.0 / denom
438  else:
439  self.boltz_wts[i] = 1.0 / np.sqrt(denom**2 + (eqm1[i]-denom)**2)
440  else:
441  self.boltz_wts = np.ones(self.ns)
442 
443  # At this point, self.fqm is a (number of snapshots) x (3 x number of atoms) array.
444  # Now we can transform it into a (number of snapshots) x (3 x number of residues + 3 x number of residues) array.
445  if self.use_nft:
446  self.nftqm = []
447  for i in range(len(self.fqm)):
448  self.nftqm.append(self.compute_netforce_torque(self.mol.xyzs[i], self.fqm[i]))
449  self.nftqm = np.array(self.nftqm)
450  self.fref = np.hstack((self.fqm, self.nftqm))
451  else:
452  self.fref = self.fqm
453  self.nnf = 0
454  self.ntq = 0
456  # Normalize Boltzmann weights.
457  self.boltz_wts /= sum(self.boltz_wts)
458 
459  def indicate(self):
460  Headings = ["Observable", "Difference\n(Calc-Ref)", "Denominator\n RMS (Ref)", " Percent \nDifference", "Term", "x Wt =", "Contrib."]
461  Data = OrderedDict([])
462  if self.energy:
463  Data['Energy (kJ/mol)'] = ["%8.4f" % self.e_err,
464  "%8.4f" % self.e_ref,
465  "%.4f%%" % (self.e_err_pct*100),
466  "%8.4f" % self.e_trm,
467  "%.3f" % self.w_energy,
468  "%8.4f" % self.e_ctr]
469  if self.force:
470  Data['Gradient (kJ/mol/A)'] = ["%8.4f" % (self.f_err/10),
471  "%8.4f" % (self.f_ref/10),
472  "%.4f%%" % (self.f_err_pct*100),
473  "%8.4f" % self.f_trm,
474  "%.3f" % self.w_force,
475  "%8.4f" % self.f_ctr]
476  if self.use_nft:
477  Data['Net Force (kJ/mol/A)'] = ["%8.4f" % (self.nf_err/10),
478  "%8.4f" % (self.nf_ref/10),
479  "%.4f%%" % (self.nf_err_pct*100),
480  "%8.4f" % self.nf_trm,
481  "%.3f" % self.w_netforce,
482  "%8.4f" % self.nf_ctr]
483  Data['Torque (kJ/mol/rad)'] = ["%8.4f" % self.tq_err,
484  "%8.4f" % self.tq_ref,
485  "%.4f%%" % (self.tq_err_pct*100),
486  "%8.4f" % self.tq_trm,
487  "%.3f" % self.w_torque,
488  "%8.4f" % self.tq_ctr]
489  if self.resp:
490  Data['Potential (a.u.'] = ["%8.4f" % (self.esp_err/10),
491  "%8.4f" % (self.esp_ref/10),
492  "%.4f%%" % (self.esp_err_pct*100),
493  "%8.4f" % self.esp_trm,
494  "%.3f" % self.w_resp,
495  "%8.4f" % self.esp_ctr]
496  self.printcool_table(data=Data, headings=Headings, color=0)
497  if self.force:
498  logger.info("Maximum force difference on atom %i (%s), frame %i, %8.4f kJ/mol/A\n" % (self.maxfatom, self.mol.elem[self.fitatoms[self.maxfatom]], self.maxfshot, self.maxdf/10))
499 
500  def energy_all(self):
501  if hasattr(self, 'engine'):
502  return self.engine.energy().reshape(-1,1)
503  else:
504  logger.error("Target must contain an engine object\n")
505  raise NotImplementedError
506 
507  def energy_force_all(self):
508  if hasattr(self, 'engine'):
509  return self.engine.energy_force()
510  else:
511  logger.error("Target must contain an engine object\n")
512  raise NotImplementedError
513 
514  def energy_force_transform(self):
515  if self.force:
516  M = self.energy_force_all()
517  selct = [0] + list(itertools.chain(*[[1+3*i+j for j in range(3)] for i in self.fitatoms]))
518  M = M[:, selct]
519  if self.use_nft:
520  Nfts = []
521  for i in range(len(M)):
522  Fm = M[i][1:]
523  Nft = self.compute_netforce_torque(self.mol.xyzs[i], Fm)
524  Nfts.append(Nft)
525  Nfts = np.array(Nfts)
526  return np.hstack((M, Nfts))
527  else:
528  return M
529  else:
530  return self.energy_all()
531 
532  def energy_one(self, i):
533  if hasattr(self, 'engine'):
534  return self.engine.energy_one(i)
535  else:
536  logger.error("Target must contain an engine object\n")
537  raise NotImplementedError
538 
539  def energy_force_one(self, i):
540  if hasattr(self, 'engine'):
541  return self.engine.energy_force_one(i)
542  else:
543  logger.error("Target must contain an engine object\n")
544  raise NotImplementedError
545 
546  def energy_force_transform_one(self,i):
547  if self.force:
548  M = self.energy_force_one(i)
549  selct = [0] + list(itertools.chain(*[[1+3*i+j for j in range(3)] for i in self.fitatoms]))
550  M = M[:, selct]
551  if self.use_nft:
552  Fm = M[1:]
553  Nft = self.compute_netforce_torque(self.mol.xyzs[i], Fm)
554  return np.hstack((M, Nft))
555  else:
556  return M
557  else:
558  return self.energy_one()
559 
560  def get_energy_force(self, mvals, AGrad=False, AHess=False):
561  """
562  LPW 7-13-2016
563 
564  This code computes the least squares objective function for the energy and force.
565  The most recent revision simplified the code to make it easier to maintain,
566  and to remove the covariance matrix and dual-weight features.
567 
568  The equations have also been simplified. Previously I normalizing each force
569  component (or triples of components belonging to an atom) separately.
570  I was also computing the objective function separately from the "indicators",
571  which led to confusion regarding why they resulted in different values.
572  The code was structured around computing the objective function by multiplying
573  an Applequist polytensor containing both energy and force with an inverse
574  covariance matrix, but it became apparent over time and experience that the
575  more complicated approach was not worth it, given the opacity that it introduced
576  into how things were computed.
577 
578  In the new code, the objective function is computed in a simple way.
579  For the energies we compute a weighted sum of squared differences
580  between E_MM and E_QM, minus (optionally) the mean energy gap, for the numerator.
581  The weighted variance of the QM energies <E_QM^2>-<E_QM>^2 is the denominator.
582  The user-supplied w_energy option is a prefactor that multiplies this term.
583  If w_normalize is set to True (no longer the default), the prefactor is
584  further divided by the sum of all of the weights.
585  The indicators are set to the square roots of the numerator and denominator above.
586 
587  For the forces we compute the same weighted sum, where each term in the sum
588  is the norm-squared of F_MM - F_QM, with no option to subtract the mean.
589  The denominator is computed in an analogous way using the norm-squared F_QM,
590  and the prefactor is w_force. The same approach is applied if the user asks
591  for net forces and/or torques. The indicators are computed from the square
592  roots of the numerator and denominator, divided by the number of
593  atoms (molecules) for forces (net forces / torques).
594 
595  In equation form, the objective function is given by:
596 
597  \[ = {W_E}\left[ {\frac{{\left( {\sum\limits_{i \in {N_s}}
598  {{w_i}{{\left( {E_i^{MM} - E_i^{QM}} \right)}^2}} } \right) -
599  {{\left( {{{\bar E}^{MM}} - {{\bar E}^{QM}}} \right)}^2}}}
600  {{\sum\limits_{i \in {N_s}} {{w_i}{{\left(
601  {E_i^{QM} - {{\bar E}^{QM}}} \right)}^2}} }}} \right] +
602  {W_F}\left[ {\frac{{\sum\limits_{i \in {N_s}} {{w_i}\sum\limits_{a \in {N_a}}
603  {{{\left| {{\bf{F}}_{i,a}^{MM} - {\bf{F}}_{i,a}^{QM}} \right|}^2}} } }}
604  {{\sum\limits_{i \in {N_s}} {{w_i}\sum\limits_{a \in {N_a}}
605  {{{\left| {{\bf{F}}_{i,a}^{QM}} \right|}^2}} } }}} \right]\]
606 
607  In the previous code (ForTune, 2011 and previous)
608  this subroutine used analytic first derivatives of the
609  energy and force to build the derivatives of the objective function.
610  Here I will take a simplified approach, because building the analytic
611  derivatives require substantial modifications of the engine code,
612  which is unsustainable. We use a finite different calculation
613  of the first derivatives of the energies and forces to get the exact
614  first derivative and approximate second derivative of the objective function..
615 
616  @param[in] mvals Mathematical parameter values
617  @param[in] AGrad Switch to turn on analytic gradient
618  @param[in] AHess Switch to turn on analytic Hessian
619  @return Answer Contribution to the objective function
620  """
621  Answer = {}
622  # Create the new force field!!
623  pvals = self.FF.make(mvals)
624 
625  # Number of atoms containing forces being fitted
626  nat = len(self.fitatoms)
627  # Number of net forces on molecules
628  nnf = self.nnf
629  # Number of torques on molecules
630  ntq = self.ntq
631  # Basically the size of the matrix
632  NC = 3*nat
633  NCP1 = 3*nat+1
634  NParts = 1
635  if self.force:
636  NParts += 1
637  if self.use_nft:
638  NParts += 2
639  NCP1 += 3*(nnf + ntq)
640  NP = self.FF.np
641  NS = self.ns
642  #==============================================================#
643  # STEP 1: Form all of the arrays. #
644  #==============================================================#
645  if (self.w_energy == 0.0 and self.w_force == 0.0 and self.w_netforce == 0.0 and self.w_torque == 0.0):
646  AGrad = False
647  AHess = False
648  # Sum of all the weights
649  Z = 0.0
650  # All vectors with NCP1 elements are ordered as
651  # [E F_1x F_1y F_1z F_2x ... NF_1x NF_1y ... TQ_1x TQ_1y ... ]
652  # Vector of QM-quantities
653  Q = np.zeros(NCP1)
654  # Mean quantities over the trajectory
655  M0 = np.zeros(NCP1)
656  Q0 = np.zeros(NCP1)
657  X0 = np.zeros(NCP1)
658  # The mean squared QM-quantities
659  QQ0 = np.zeros(NCP1)
660  # Derivatives of the MM-quantity
661  M_p = np.zeros((NP,NCP1))
662  M_pp = np.zeros((NP,NCP1))
663  # Means of gradients
664  M0_p = np.zeros((NP,NCP1))
665  M0_pp = np.zeros((NP,NCP1))
666  # Vector of objective function terms
667  SPX = np.zeros(NCP1)
668  if AGrad:
669  SPX_p = np.zeros((NP,NCP1))
670  # Derivatives of "parts" of objective functions - i.e.
671  # the sum is taken over the components of force, net force, torque
672  # but the components haven't been weighted and summed.
673  X2_Parts_p = np.zeros((NP,NParts))
674  if AHess:
675  SPX_pq = np.zeros((NP,NP,NCP1))
676  X2_Parts_pq = np.zeros((NP,NP,NParts))
677  # Storage of the MM-quantities and derivatives for all snapshots.
678  # This saves time because we don't need to execute the external program
679  # once per snapshot, but requires memory.
680  M_all = np.zeros((NS,NCP1))
681  if AGrad and self.all_at_once:
682  dM_all = np.zeros((NS,NP,NCP1))
683  ddM_all = np.zeros((NS,NP,NCP1))
684  #==============================================================#
685  # STEP 2: Loop through the snapshots. #
686  #==============================================================#
687  if self.all_at_once:
688  logger.debug("\rExecuting\r")
689  M_all = self.energy_force_transform()
690  if self.asym:
691  M_all[:, 0] -= M_all[self.smin, 0]
692  if AGrad or AHess:
693  def callM(mvals_):
694  logger.debug("\r")
695  pvals = self.FF.make(mvals_)
696  return self.energy_force_transform()
697  for p in self.pgrad:
698  dM_all[:,p,:], ddM_all[:,p,:] = f12d3p(fdwrap(callM, mvals, p), h = self.h, f0 = M_all)
699  if self.asym:
700  dM_all[:, p, 0] -= dM_all[self.smin, p, 0]
701  ddM_all[:, p, 0] -= ddM_all[self.smin, p, 0]
702  if self.force and not in_fd():
703  self.maxfatom = -1
704  self.maxfshot = -1
705  self.maxdf = 0.0
706  for i in range(NS):
707  if i % 100 == 0:
708  logger.debug("\rIncrementing quantities for snapshot %i\r" % i)
709  # Build Boltzmann weights and increment partition function.
710  P = self.boltz_wts[i]
711  Z += P
712  # Load reference (QM) data
713  Q[0] = self.eqm[i]
714  if self.force:
715  Q[1:] = self.fref[i,:].copy()
716  QQ = Q*Q
717  # Call the simulation software to get the MM quantities
718  # (or load from M_all array)
719  if self.all_at_once:
720  M = M_all[i]
721  else:
722  if i % 100 == 0:
723  logger.debug("Shot %i\r" % i)
724  M = self.energy_force_transform_one(i)
725  M_all[i,:] = M.copy()
726  # MM - QM difference
727  X = M-Q
728  boost = 1.0
729  # For asymmetric fit, MM energies lower than QM are given a boost factor
730  if self.asym and X[0] < 0.0:
731  boost = self.energy_asymmetry
732  # Save information about forces
733  if self.force:
734  # Norm-squared of force differences for each atom
735  dfrc2 = norm2(M-Q, 1, nat)
736  if not in_fd() and np.max(dfrc2) > self.maxdf:
737  self.maxdf = np.sqrt(np.max(dfrc2))
738  self.maxfatom = np.argmax(dfrc2)
739  self.maxfshot = i
740  # Increment the average quantities
741  # The [0] indicates that we are fitting the RMS force and not the RMSD
742  # (without the covariance, subtracting a mean force doesn't make sense.)
743  # The rest of the array is empty.
744  M0[0] += P*M[0]
745  Q0[0] += P*Q[0]
746  X0[0] += P*X[0]
747  # We store all elements of the mean-squared QM quantities.
748  QQ0 += P*QQ
749  # Increment the objective function.
750  Xi = X**2
751  Xi[0] *= boost
752  # SPX contains the sum over snapshots
753  SPX += P * Xi
754  #==============================================================#
755  # STEP 2a: Increment gradients and mean quantities. #
756  #==============================================================#
757  for p in self.pgrad:
758  if not AGrad: continue
759  if self.all_at_once:
760  M_p[p] = dM_all[i, p]
761  M_pp[p] = ddM_all[i, p]
762  else:
763  def callM(mvals_):
764  if i % 100 == 0:
765  logger.debug("\r")
766  pvals = self.FF.make(mvals_)
767  return self.energy_force_transform_one(i)
768  M_p[p],M_pp[p] = f12d3p(fdwrap(callM, mvals, p), h = self.h, f0 = M)
769  if all(M_p[p] == 0): continue
770  M0_p[p][0] += P * M_p[p][0]
771  Xi_p = 2 * X * M_p[p]
772  Xi_p[0] *= boost
773  SPX_p[p] += P * Xi_p
774  if not AHess: continue
775  if self.all_at_once:
776  M_pp[p] = ddM_all[i, p]
777  # This formula is more correct, but perhapsively convergence is slower.
778  #Xi_pq = 2 * (M_p[p] * M_p[p] + X * M_pp[p])
779  # Gauss-Newton formula for approximate Hessian
780  Xi_pq = 2 * (M_p[p] * M_p[p])
781  Xi_pq[0] *= boost
782  SPX_pq[p,p] += P * Xi_pq
783  for q in range(p):
784  if all(M_p[q] == 0): continue
785  if q not in self.pgrad: continue
786  Xi_pq = 2 * M_p[p] * M_p[q]
787  Xi_pq[0] *= boost
788  SPX_pq[p,q] += P * Xi_pq
789 
790  #==============================================================#
791  # STEP 2b: Write energies and forces to disk. #
792  #==============================================================#
793  M_all_print = M_all.copy()
794  if self.savg:
795  M_all_print[:,0] -= np.average(M_all_print[:,0], weights=self.boltz_wts)
796  if self.force:
797  Q_all_print = np.hstack((col(self.eqm),self.fref))
798  else:
799  Q_all_print = col(self.eqm)
800  if self.savg:
801  QEtmp = np.array(Q_all_print[:,0]).flatten()
802  Q_all_print[:,0] -= np.average(QEtmp, weights=self.boltz_wts)
803  if self.attenuate:
804  QEtmp = np.array(Q_all_print[:,0]).flatten()
805  Q_all_print[:,0] -= np.min(QEtmp)
806  MEtmp = np.array(M_all_print[:,0]).flatten()
807  M_all_print[:,0] -= np.min(MEtmp)
808  if self.writelevel > 1:
809  np.savetxt('M.txt',M_all_print)
810  np.savetxt('Q.txt',Q_all_print)
811  if self.writelevel > 0:
812  EnergyComparison = np.hstack((col(Q_all_print[:,0]),
813  col(M_all_print[:,0]),
814  col(M_all_print[:,0])-col(Q_all_print[:,0]),
815  col(self.boltz_wts)))
816  np.savetxt("EnergyCompare.txt", EnergyComparison, header="%11s %12s %12s %12s" % ("QMEnergy", "MMEnergy", "Delta(MM-QM)", "Weight"), fmt="% 12.6e")
817  if self.writelevel > 1:
818  plot_qm_vs_mm(Q_all_print[:,0], M_all_print[:,0],
819  M_orig=self.M_orig[:,0] if self.M_orig is not None else None,
820  title='Abinitio '+self.name)
821  if self.M_orig is None:
822  self.M_orig = M_all_print.copy()
823  if self.force and self.writelevel > 1:
824  # Write .xyz files which can be viewed in vmd.
825  QMTraj = self.mol[:].atom_select(self.fitatoms)
826  Mforce_obj = QMTraj[:]
827  Qforce_obj = QMTraj[:]
828  Mforce_print = np.array(M_all_print[:,1:3*nat+1])
829  Qforce_print = np.array(Q_all_print[:,1:3*nat+1])
830  MaxComp = np.max(np.abs(np.vstack((Mforce_print,Qforce_print)).flatten()))
831  Mforce_print /= MaxComp
832  Qforce_print /= MaxComp
833  for i in range(NS):
834  Mforce_obj.xyzs[i] = Mforce_print[i, :].reshape(-1,3)
835  Qforce_obj.xyzs[i] = Qforce_print[i, :].reshape(-1,3)
836  # if nat < len(self.qmatoms):
837  # Fpad = np.zeros((len(self.qmatoms) - nat, 3))
838  # Mforce_obj.xyzs[i] = np.vstack((Mforce_obj.xyzs[i], Fpad))
839  # Qforce_obj.xyzs[i] = np.vstack((Qforce_obj.xyzs[i], Fpad))
840  if Mforce_obj.na != Mforce_obj.xyzs[0].shape[0]:
841  print(Mforce_obj.na)
842  print(Mforce_obj.xyzs[0].shape[0])
843  warn_once('\x1b[91mThe printing of forces is not set up correctly. Not printing forces. Please report this issue.\x1b[0m')
844  else:
845  if self.writelevel > 1:
846  QMTraj.write('coords.xyz')
847  Mforce_obj.elem = ['H' for i in range(Mforce_obj.na)]
848  Mforce_obj.write('MMforce.xyz')
849  Qforce_obj.elem = ['H' for i in range(Qforce_obj.na)]
850  Qforce_obj.write('QMforce.xyz')
851 
852  #==============================================================#
853  # STEP 3: Build the objective function. #
854  #==============================================================#
855 
856  logger.debug("Done with snapshots, building objective function now\r")
857  W_Components = np.zeros(NParts)
858  W_Components[0] = self.w_energy
859  if self.force:
860  W_Components[1] = self.w_force
861  if self.use_nft:
862  W_Components[2] = self.w_netforce
863  W_Components[3] = self.w_torque
864  if np.sum(W_Components) > 0 and self.w_normalize:
865  W_Components /= np.sum(W_Components)
866 
867  if self.energy_rms_override != 0.0:
868  QQ0[0] = self.energy_rms_override ** 2
869  Q0[0] = 0.0
870 
871  if self.force_rms_override != 0.0:
872  # x 100 to match unit from kJ/mol/nm to kJ/mol/A
873  # / 3 to match the number of force component = 3 * nat
874  QQ0[1:3*nat+1] = self.force_rms_override ** 2 * 100 / 3
875  Q0[1:3*nat+1] = 0.0
876 
877  def compute_objective(SPX_like,divide=1,L=None,R=None,L2=None,R2=None):
878  a = 0
879  n = 1
880  X2E = compute_objective_part(SPX_like,QQ0,Q0,Z,a,n,energy=True,subtract_mean=self.savg,
881  divide=divide,L=L,R=R,L2=L2,R2=R2)
882  objs = [X2E]
883  if self.force:
884  a = 1
885  n = 3*nat
886  X2F = compute_objective_part(SPX_like,QQ0,Q0,Z,a,n,divide=divide)
887  objs.append(X2F)
888  if self.use_nft:
889  a += n
890  n = 3*nnf
891  X2N = compute_objective_part(SPX_like,QQ0,Q0,Z,a,n,divide=divide)
892  objs.append(X2N)
893  a += n
894  n = 3*ntq
895  X2T = compute_objective_part(SPX_like,QQ0,Q0,Z,a,n,divide=divide)
896  objs.append(X2T)
897  return np.array(objs)
898 
899  # The objective function components (i.e. energy, force, net force, torque)
900  X2_Components = compute_objective(SPX,L=X0,R=X0)
901  # The squared residuals before they are normalized
902  X2_Physical = compute_objective(SPX,divide=0,L=X0,R=X0)
903  # The normalization factors
904  X2_Normalize = compute_objective(SPX,divide=2,L=X0,R=X0)
905  # The derivatives of the objective function components
906  for p in self.pgrad:
907  if not AGrad: continue
908  X2_Parts_p[p,:] = compute_objective(SPX_p[p],L=2*X0,R=M0_p[p])
909  if not AHess: continue
910  X2_Parts_pq[p,p,:] = compute_objective(SPX_pq[p,p],L=2*M0_p[p],R=M0_p[p],L2=2*X0,R2=M0_pp[p])
911  for q in range(p):
912  if q not in self.pgrad: continue
913  X2_Parts_pq[p,q,:] = compute_objective(SPX_pq[p,q],L=2*M0_p[p],R=M0_p[q])
914  # Get the other half of the Hessian matrix.
915  X2_Parts_pq[q,p,:] = X2_Parts_pq[p,q,:]
916  # The objective function as a weighted sum of the components
917  X2 = np.dot(X2_Components, W_Components)
918  # Derivatives of the objective function
919  G = np.zeros(NP)
920  H = np.zeros((NP,NP))
921  for p in self.pgrad:
922  if not AGrad: continue
923  G[p] = np.dot(X2_Parts_p[p], W_Components)
924  if not AHess: continue
925  for q in self.pgrad:
926  H[p,q] = np.dot(X2_Parts_pq[p,q], W_Components)
927 
928  #==============================================================#
929  # STEP 3a: Build the indicators. #
930  #==============================================================#
931  # Save values to qualitative indicator if not inside finite difference code.
932  if not in_fd():
933  # Contribution from energy and force parts.
934  tw = self.w_energy + self.w_force + self.w_netforce + self.w_torque + self.w_resp
935  self.e_trm = X2_Components[0]
936  self.e_ctr = X2_Components[0]*W_Components[0]
937  if self.w_normalize: self.e_ctr /= tw
938  self.e_ref = np.sqrt(X2_Normalize[0])
939  self.e_err = np.sqrt(X2_Physical[0])
940  self.e_err_pct = self.e_err/self.e_ref
941  if self.force:
942  self.f_trm = X2_Components[1]
943  self.f_ctr = X2_Components[1]*W_Components[1]
944  if self.w_normalize: self.f_ctr /= tw
945  self.f_ref = np.sqrt(X2_Normalize[1]/nat)
946  self.f_err = np.sqrt(X2_Physical[1]/nat)
947  self.f_err_pct = self.f_err/self.f_ref
948  if self.use_nft:
949  self.nf_trm = X2_Components[2]
950  self.nf_ctr = X2_Components[2]*W_Components[2]
951  self.nf_ref = np.sqrt(X2_Normalize[2]/nnf)
952  self.nf_err = np.sqrt(X2_Physical[2]/nnf)
953  self.nf_err_pct = self.nf_err/self.nf_ref
954  self.tq_trm = X2_Components[3]
955  self.tq_ctr = X2_Components[3]*W_Components[3]
956  self.tq_ref = np.sqrt(X2_Normalize[3]/ntq)
957  self.tq_err = np.sqrt(X2_Physical[3]/ntq)
958  self.tq_err_pct = self.tq_err/self.tq_ref
959  if self.w_normalize:
960  self.nf_ctr /= tw
961  self.tq_ctr /= tw
963  pvals = self.FF.make(mvals) # Write a force field that isn't perturbed by finite differences.
964  Answer = {'X':X2, 'G':G, 'H':H}
965  return Answer
966 
967  def get_resp(self, mvals, AGrad=False, AHess=False):
968  """ Electrostatic potential fitting. Implements the RESP objective function. (In Python so obviously not optimized.) """
969  if (self.w_resp == 0.0):
970  AGrad = False
971  AHess = False
972  Answer = {}
973  pvals = self.FF.make(mvals)
974 
975  # Build the distance matrix for ESP fitting.
976  self.invdists = self.build_invdist(mvals)
977 
978  NS = self.ns
979  NP = self.FF.np
980  Z = 0
981  Y = 0
982  def new_charges(mvals_):
983  """ Return the charges acting on the system. """
984  logger.debug("\r")
985  pvals = self.FF.make(mvals_)
986  return self.engine.get_charges()
987 
988  # Need to update the positions of atoms, if there are virtual sites.
989  # qvals = [pvals[i] for i in self.FF.qmap]
990  # # All of a sudden I need the number of virtual sites.
991  # qatoms = np.zeros(self.nparticles)
992  # for i, jj in enumerate(self.FF.qid):
993  # for j in jj:
994  # qatoms[j] = qvals[i]
995  # return qatoms
996 
997  # Obtain a derivative matrix the stupid way
998  charge0 = new_charges(mvals)
999  if AGrad:
1000  # dqPdqM = []
1001  # for i in range(NP):
1002  # print "Now working on parameter number", i
1003  # dqPdqM.append(f12d3p(fdwrap(new_charges,mvals,i), h = self.h)[0])
1004  # dqPdqM = np.matrix(dqPdqM).T
1005  dqPdqM = np.array([(f12d3p(fdwrap(new_charges,mvals,i), h = self.h, f0 = charge0)[0] if i in self.pgrad else np.zeros_like(charge0)) for i in range(NP)]).T
1006  xyzs = np.array(self.mol.xyzs)
1007  espqvals = np.array(self.espval)
1008  espxyz = np.array(self.espxyz)
1009 
1010  ddVdqPdVS = {}
1011  # Second derivative of the inverse distance matrix with respect to the virtual site position
1012  dddVdqPdVS2 = {}
1013  if AGrad:
1014  for p in self.pgrad:
1015  if 'VSITE' in self.FF.plist[p]:
1016  ddVdqPdVS[p], dddVdqPdVS2[p] = f12d3p(fdwrap(self.build_invdist,mvals,p), h = self.h, f0 = self.invdists)
1017  X = 0
1018  Q = 0
1019  D = 0
1020  G = np.zeros(NP)
1021  H = np.zeros((NP, NP))
1022  for i in range(self.ns):
1023  P = self.boltz_wts[i]
1024  Z += P
1025  dVdqP = np.array(self.invdists[i])
1026  espqval = espqvals[i]
1027  espmval = np.dot(dVdqP, col(new_charges(mvals)))
1028  desp = flat(espmval) - espqval
1029  X += P * np.dot(desp, desp) / self.nesp
1030  Q += P * np.dot(espqval, espqval) / self.nesp
1031  D += P * (np.dot(espqval, espqval) / self.nesp - (np.sum(espqval) / self.nesp)**2)
1032  if AGrad:
1033  dVdqM = np.dot(dVdqP, dqPdqM).T
1034  for p, vsd in ddVdqPdVS.items():
1035  dVdqM[p,:] += flat(np.dot(vsd[i], col(new_charges(mvals))))
1036  G += flat(P * 2 * np.dot(dVdqM, col(desp))) / self.nesp
1037  if AHess:
1038  d2VdqM2 = np.zeros(dVdqM.shape)
1039  for p, vsd in dddVdqPdVS2.items():
1040  d2VdqM2[p,:] += flat(np.dot(vsd[i], col(new_charges(mvals))))
1041  H += np.array(P * 2 * (np.dot(dVdqM, dVdqM.T) + np.dot(d2VdqM2, col(desp)))) / self.nesp
1042  # Redundant but we keep it anyway
1043  D /= Z
1044  X /= Z
1045  X /= D
1046  G /= Z
1047  G /= D
1048  H /= Z
1049  H /= D
1050  Q /= Z
1051  Q /= D
1052  if not in_fd():
1053  self.esp_err = np.sqrt(X)
1054  self.esp_ref = np.sqrt(Q)
1055  self.esp_err_pct = self.esp_err / self.esp_ref
1056 
1057  # Following is the restraint part
1058  # RESP hyperbola "strength" parameter; 0.0005 is weak, 0.001 is strong
1059  # RESP hyperbola "tightness" parameter; don't need to change this
1060  a = self.resp_a
1061  b = self.resp_b
1062  q = new_charges(mvals)
1063  R = a*np.sum((q**2 + b**2)**0.5 - b)
1064  dR = a*q*(q**2 + b**2)**-0.5
1065  ddR = a*b**2*(q**2 + b**2)**-1.5
1066  self.respterm = R
1067  X += R
1068  if AGrad:
1069  G += flat(np.dot(dqPdqM.T, col(dR)))
1070  if AHess:
1071  H += np.diag(flat(np.dot(dqPdqM.T, col(ddR))))
1073  if not in_fd():
1074  self.esp_trm = X
1075  self.esp_ctr = X*self.w_resp
1076  if self.w_normalize:
1077  tw = self.w_energy + self.w_force + self.w_netforce + self.w_torque + self.w_resp
1078  self.esp_ctr /= tw
1079  Answer = {'X':X,'G':G,'H':H}
1080  return Answer
1082  def get(self, mvals, AGrad=False, AHess=False):
1083  Answer = {'X':0.0, 'G':np.zeros(self.FF.np), 'H':np.zeros((self.FF.np, self.FF.np))}
1084  tw = self.w_energy + self.w_force + self.w_netforce + self.w_torque + self.w_resp
1085  if self.w_normalize:
1086  w_ef /= tw
1087  w_resp = self.w_resp / tw
1088  else:
1089  w_ef = 1.0
1090  w_resp = self.w_resp
1091  if self.energy or self.force:
1092  Answer_EF = self.get_energy_force(mvals, AGrad, AHess)
1093  for i in Answer_EF:
1094  Answer[i] += w_ef * Answer_EF[i]
1095  if self.resp:
1096  Answer_ESP = self.get_resp(mvals, AGrad, AHess)
1097  for i in Answer_ESP:
1098  Answer[i] += w_resp * Answer_ESP[i]
1099  if not any([self.energy, self.force, self.resp]):
1100  logger.error("Ab initio fitting must have at least one of: Energy, Force, ESP\n")
1101  raise RuntimeError
1102  if not in_fd():
1103  self.objective = Answer['X']
1104  return Answer
1105 
1106 def compute_objective_part(SPX,QQ0,Q0,Z,a,n,energy=False,subtract_mean=False,divide=1,L=None,R=None,L2=None,R2=None):
1107  # Divide by Z to normalize
1108  XiZ = SPX[a:a+n]/Z
1109  QQ0iZ = QQ0[a:a+n]/Z
1110  Q0iZ = Q0[a:a+n]/Z
1111  # Subtract out the product of L and R if provided.
1112  if subtract_mean:
1113  if L is not None and R is not None:
1114  LiZ = L[a:a+n]/Z
1115  RiZ = R[a:a+n]/Z
1116  XiZ -= LiZ*RiZ
1117  elif L2 is not None and R2 is not None:
1118  L2iZ = L2[a:a+n]/Z
1119  R2iZ = R2[a:a+n]/Z
1120  XiZ -= L2iZ*R2iZ
1121  else:
1122  raise RuntimeError("subtract_mean is set to True, must provide L/R or L2/R2")
1123  if energy:
1124  QQ0iZ -= Q0iZ*Q0iZ
1125 
1126  # Return the answer.
1127  if divide == 1:
1128  X2 = np.sum(XiZ)/np.sum(QQ0iZ)
1129  elif divide == 0:
1130  X2 = np.sum(XiZ)
1131  elif divide == 2:
1132  X2 = np.sum(QQ0iZ)
1133  else:
1134  raise RuntimeError('Please pass either 0, 1, 2 to divide')
1135  return X2
1136 
1137 def plot_qm_vs_mm(Q, M, M_orig=None, title=''):
1138  import matplotlib.pyplot as plt
1139  qm_min_dx = np.argmin(Q)
1140  e_qm = Q - Q[qm_min_dx]
1141  e_mm = M - M[qm_min_dx]
1142  if M_orig is not None:
1143  e_mm_orig = M_orig - M_orig[qm_min_dx]
1144  plt.plot(e_qm, e_mm_orig, 'x', markersize=5, label='Orig.')
1145  plt.plot(e_qm, e_mm, '.', markersize=5, label='Current')
1146  plt.xlabel('QM Energies (kJ/mol)')
1147  plt.ylabel('MM Energies (kJ/mol)')
1148  x1,x2,y1,y2 = plt.axis()
1149  x1 = min(x1, y1)
1150  y1 = x1
1151  x2 = max(x2, y2)
1152  y2 = x2
1153  rng = x2-x1
1154  plt.axis('equal')
1155  plt.axis([x1-0.05*rng, x2+0.05*rng, y1-0.05*rng, y2+0.05*rng])
1156  plt.plot([x1,x2],[y1,y2], '--' )
1157  plt.legend(loc='lower right')
1158  plt.title(title)
1159  fig = plt.gcf()
1160  fig.set_size_inches(5,5)
1161  fig.savefig('e_qm_vs_mm.pdf')
1162  plt.close()
fqm
Reference (QM) forces.
Definition: abinitio.py:166
savg
Option for how much data to write to disk.
Definition: abinitio.py:147
def compute_netforce_torque(self, xyz, force, QM=False)
Definition: abinitio.py:245
def read_reference_data(self)
Read the reference ab initio data from a file such as qdata.txt.
Definition: abinitio.py:360
save_vmvals
Save the mvals from the last time we updated the vsites.
Definition: abinitio.py:212
buildx
Read in the reference data.
Definition: abinitio.py:210
Nifty functions, intended to be imported by any module within ForceBalance.
use_nft
Whether to compute net forces and torques, or not.
Definition: abinitio.py:185
AtomLists
Lists of atoms to do net force/torque fitting and excluding virtual sites.
Definition: abinitio.py:204
def energy_force_transform(self)
Definition: abinitio.py:519
f_err
Qualitative Indicator: average force error (fractional)
Definition: abinitio.py:177
espval
ESP values.
Definition: abinitio.py:170
esp_err
Qualitative Indicator: "relative RMS" for electrostatic potential.
Definition: abinitio.py:180
def energy_all(self)
Definition: abinitio.py:505
espxyz
ESP grid points.
Definition: abinitio.py:168
def indicate(self)
Definition: abinitio.py:464
eqm
Reference (QM) energies.
Definition: abinitio.py:164
def isint(word)
ONLY matches integers! If you have a decimal point? None shall pass!
Definition: molecule.py:401
w_force
Initialize the base class.
Definition: abinitio.py:121
def flat(vec)
Given any list, array, or matrix, return a single-index array.
Definition: nifty.py:467
nparticles
The number of (atoms + drude particles + virtual sites)
Definition: abinitio.py:197
def plot_qm_vs_mm(Q, M, M_orig=None, title='')
Definition: abinitio.py:1145
mol
Read in the trajectory file.
Definition: abinitio.py:188
def in_fd()
Invoking this function from anywhere will tell us whether we&#39;re being called by a finite-difference f...
def compute_objective_part(SPX, QQ0, Q0, Z, a, n, energy=False, subtract_mean=False, divide=1, L=None, R=None, L2=None, R2=None)
Definition: abinitio.py:1114
def fdwrap(func, mvals0, pidx, key=None, kwargs)
A function wrapper for finite difference designed for differentiating &#39;get&#39;-type functions.
def build_invdist(self, mvals)
Definition: abinitio.py:216
def col(vec)
Given any list, array, or matrix, return a 1-column 2D array.
Definition: nifty.py:448
def commadash(l)
Definition: nifty.py:239
def energy_one(self, i)
Definition: abinitio.py:537
def norm2(arr, a=0, n=None, step=3)
Given a one-dimensional array, return the norm-squared of every "step" elements, starting at &#39;a&#39; and ...
Definition: abinitio.py:47
def __init__(self, options, tgt_opts, forcefield)
Initialization; define a few core concepts.
Definition: abinitio.py:94
def warn_press_key(warning, timeout=10)
Definition: nifty.py:1599
def energy_force_transform_one(self, i)
Definition: abinitio.py:551
def energy_force_one(self, i)
Definition: abinitio.py:544
engine
Build keyword dictionaries to pass to engine.
Definition: abinitio.py:202
boltz_wts
Boltzmann weights.
Definition: abinitio.py:162
def get_resp(self, mvals, AGrad=False, AHess=False)
Electrostatic potential fitting.
Definition: abinitio.py:975
def get(self, mvals, AGrad=False, AHess=False)
Definition: abinitio.py:1090
def warn_once(warning, warnhash=None)
Prints a warning but will only do so once in a given run.
Definition: nifty.py:1611
def get_energy_force(self, mvals, AGrad=False, AHess=False)
LPW 7-13-2016.
Definition: abinitio.py:626
def f12d3p(f, h, f0=None)
A three-point finite difference stencil.
Subclass of Target for fitting force fields to ab initio data.
Definition: abinitio.py:84
qfnm
The qdata.txt file that contains the QM energies and forces.
Definition: abinitio.py:172
def uncommadash(s)
Definition: nifty.py:249
e_err
Qualitative Indicator: average energy error (in kJ/mol)
Definition: abinitio.py:174
loop_over_snapshots
LPW 2018-02-11: This is set to True if the target calculates a single-point property over several exi...
Definition: abinitio.py:160
def energy_force_all(self)
Definition: abinitio.py:512