1 """@package forcebalance.counterpoise 3 Match an empirical potential to the counterpoise correction for basis set superposition error (BSSE). 5 Here we test two different functional forms: a three-parameter 6 Gaussian repulsive potential and a four-parameter Gaussian which 7 goes smoothly to an exponential. The latter can be written in two 8 different ways - one which gives us control over the exponential, 9 the switching distance and the Gaussian decay constant, and 10 another which gives us control over the Gaussian and the switching 11 distance. They are called 'CPGAUSS', 'CPEXPG', and 'CPGEXP'. I think 12 the third option is the best although our early tests have 13 indicated that none of the force fields perform particularly well 16 This subclass of Target implements the 'get' method. 21 from __future__
import absolute_import
23 from builtins
import range
27 from forcebalance.target
import Target
30 from forcebalance.output
import getLogger
31 logger = getLogger(__name__)
34 """ Target subclass for matching the counterpoise correction.""" 36 def __init__(self,options,tgt_opts,forcefield):
37 """ To instantiate Counterpoise, we read the coordinates and counterpoise data.""" 39 super(Counterpoise,self).
__init__(options,tgt_opts,forcefield)
45 self.set_option(tgt_opts,
'shots',
'ns')
54 self.elem, self.
xyzs = self.
loadxyz(os.path.join(self.root,self.tgtdir,
'all.xyz'))
56 self.
cpqm = self.
load_cp(os.path.join(self.root,self.tgtdir,
'cp.dat'))
59 """ Parse an XYZ file which contains several xyz coordinates, and return their elements. 61 @param[in] fnm The input XYZ file name 62 @return elem A list of chemical elements in the XYZ file 63 @return xyzs A list of XYZ coordinates (number of snapshots times number of atoms) 64 @todo I should probably put this into a more general library for reading coordinates. 67 logger.info(
"Loading XYZ file!\n")
73 for line
in open(fnm):
76 if match(
'^[0-9]+$',strip):
79 elif match(
'[A-Z][a-z]*( +[-+]?([0-9]*\.[0-9]+|[0-9]+)){3}$',strip):
80 xyz.append([float(i)
for i
in sline[1:]])
81 if len(elem) < self.
na:
85 xyzs.append(np.array(xyz))
95 """ Load in the counterpoise data, which is easy; the file 96 consists of floating point numbers separated by newlines. """ 97 logger.info(
"Loading CP Data!\n")
98 return np.array([float(i.strip())
for i
in open(fnm).readlines()])[:self.
ns]
100 def get(self,mvals,AGrad=False,AHess=False):
101 """Gets the objective function for fitting the counterpoise correction. 103 As opposed to AbInitio_GMXX2, which calls an external program, 104 this script actually computes the empirical interaction given the 105 force field parameters. 107 It loops through the snapshots and atom pairs, and computes pairwise 108 contributions to an energy term according to hard-coded functional forms. 110 One potential issue is that we go through all atom pairs instead of 111 looking only at atom pairs between different fragments. This means that 112 even for two infinitely separated fragments it will predict a finite 113 CP correction. While it might be okay to apply such a potential in practice, 114 there will be some issues for the fitting. Thus, we assume the last snapshot 115 to be CP-free and subtract that value of the potential back out. 117 Note that forces and parametric derivatives are not implemented. 119 @param[in] mvals Mathematical parameter values 120 @param[in] AGrad Switch to turn on analytic gradient (not implemented) 121 @param[in] AHess Switch to turn on analytic Hessian (not implemented) 122 @return Answer Contribution to the objective function 127 pvals = self.FF.create_pvals(mvals)
129 logger.info(
"CPMM: %s \r" % self.name)
131 for s
in range(self.
ns):
135 for i
in range(self.
na):
136 for j
in range(i+1,self.
na):
139 dx = np.linalg.norm(xyz[i,:]-xyz[j,:])
142 aiaj = ai < aj
and "%s%s" % (ai,aj)
or "%s%s" % (aj,ai)
143 pidlist = [
'CPGAUSSA',
'CPGAUSSB',
'CPGAUSSC']
144 if all([k+aiaj
in self.FF.map
for k
in pidlist]):
146 A, B, C = [self.FF.pvals[self.FF.map[k+aiaj]]
for k
in pidlist]
148 cpmm_i += A * np.exp (-B * (dx - C)**2)
150 pidlist = [
'CPGEXPA',
'CPGEXPB',
'CPGEXPG',
'CPGEXPX']
151 if all([k+aiaj
in self.FF.map
for k
in pidlist]):
152 A, B, G, X0 = [self.FF.pvals[self.FF.map[k+aiaj]]
for k
in pidlist]
156 cpmm_i += np.exp(-a*dx+b)
158 cpmm_i += np.exp(-A*(dx-B)**2+G)
159 pidlist = [
'CPEXPGA1',
'CPEXPGB',
'CPEXPGX0',
'CPEXPGA2']
160 if all([k+aiaj
in self.FF.map
for k
in pidlist]):
161 A1, B, X0, A2 = [self.FF.pvals[self.FF.map[k+aiaj]]
for k
in pidlist]
162 B2 = 2 * A2 * X0 - A1
165 cpmm_i += np.exp(-A1*dx+B)
167 cpmm_i += np.exp(-A2*dx**2 + B2*dx + G)
169 cpmm = np.array(cpmm)
172 with
wopen(
'results')
as f: f.writelines([
"% .4f % .4f\n" % (cpmm[i],self.
cpqm[i])
for i
in range(len(cpmm))])
174 dcp = cpmm - self.
cpqm 176 Answer = {
'X': np.dot(dcp,dcp),
177 'G': np.zeros(self.FF.np),
178 'H': np.zeros((self.FF.np,self.FF.np))
def get(self, mvals, AGrad=False, AHess=False)
Gets the objective function for fitting the counterpoise correction.
loop_over_snapshots
Number of snapshots.
def loadxyz(self, fnm)
Parse an XYZ file which contains several xyz coordinates, and return their elements.
cpqm
Counterpoise correction data.
xyzs
XYZ elements and coordinates.
def load_cp(self, fnm)
Load in the counterpoise data, which is easy; the file consists of floating point numbers separated b...
Target subclass for matching the counterpoise correction.
def wopen(dest, binary=False)
If trying to write to a symbolic link, remove it first.
def __init__(self, options, tgt_opts, forcefield)
To instantiate Counterpoise, we read the coordinates and counterpoise data.