1
2
3# Evaluate GTO's starting from a symbolic representation
4# see qmc_algorithms/Wavefunctions/GaussianOrbitals.ipynb
5
6from sympy import *
7from collections import namedtuple, defaultdict
8import numpy as np
9import pdb
10
11CG_basis = namedtuple('CG_basis',['orbtype','nbasis','zeta','contraction_coeff'])
12
13class GTO:
14  def __init__(self, basis=None, center=[0.0, 0.0, 0.0]):
15    x,y,z = symbols('x y z')
16    alpha = Symbol('alpha', positive=True, real=True)
17    r = Symbol('r',real=True,nonnegative=True)
18    i,j,k = symbols('i j k',integer=True)
19    N = Symbol('N')
20    self.N = N
21    norm1 = factorial(i)*factorial(j)*factorial(k)
22    norm2 = factorial(2*i)*factorial(2*j)*factorial(2*k)
23    norm_sym = (2*alpha/pi)**(3/S(4)) * sqrt((8*alpha)**(i+j+k)*norm1/norm2)
24
25    gto_sym_raw = N * x**i * y**j * z**k * exp(-alpha *r**2)
26
27    gto_sym = gto_sym_raw.subs(N, norm_sym)
28
29    self.alpha = alpha
30    self.x = x; self.y = y; self.z = z
31    self.r = r
32    self.i = i; self.j = j; self.k = k
33    self.gto_sym = gto_sym
34    self.compute_grad()
35
36    self.ijk = get_ijk_by_type()
37
38    self.basis = basis
39    self.center = center
40
41
42  def compute_grad(self):
43    r2 = self.x**2 + self.y**2 + self.z**2
44    gto_xyz = self.gto_sym.subs(self.r**2, r2)
45    #print gto_xyz
46    self.grad = [0]*3
47    self.grad[0] = diff(gto_xyz, self.x).subs(r2, self.r**2)
48    self.grad[1] = diff(gto_xyz, self.y).subs(r2, self.r**2)
49    self.grad[2] = diff(gto_xyz, self.z).subs(r2, self.r**2)
50    lap = diff(gto_xyz, self.x, 2) + \
51          diff(gto_xyz, self.y, 2) + \
52          diff(gto_xyz, self.z, 2)
53    # Need to expand to avoid NaN's
54    self.lap = expand(lap.subs(r2, self.r**2))
55
56    self.hess = [0]*6
57    self.hess[0] = diff(diff(gto_xyz,self.x),self.x).subs(r2,self.r**2)
58    self.hess[1] = diff(diff(gto_xyz,self.x),self.y).subs(r2,self.r**2)
59    self.hess[2] = diff(diff(gto_xyz,self.x),self.z).subs(r2,self.r**2)
60    self.hess[3] = diff(diff(gto_xyz,self.y),self.y).subs(r2,self.r**2)
61    self.hess[4] = diff(diff(gto_xyz,self.y),self.z).subs(r2,self.r**2)
62    self.hess[5] = diff(diff(gto_xyz,self.z),self.z).subs(r2,self.r**2)
63
64    self.ghess = [0]*10
65    self.ghess[0] = diff(diff(diff(gto_xyz,self.x),self.x),self.x).subs(r2,self.r**2)
66    self.ghess[1] = diff(diff(diff(gto_xyz,self.x),self.x),self.y).subs(r2,self.r**2)
67    self.ghess[2] = diff(diff(diff(gto_xyz,self.x),self.x),self.z).subs(r2,self.r**2)
68    self.ghess[3] = diff(diff(diff(gto_xyz,self.x),self.y),self.y).subs(r2,self.r**2)
69    self.ghess[4] = diff(diff(diff(gto_xyz,self.x),self.y),self.z).subs(r2,self.r**2)
70    self.ghess[5] = diff(diff(diff(gto_xyz,self.x),self.z),self.z).subs(r2,self.r**2)
71    self.ghess[6] = diff(diff(diff(gto_xyz,self.y),self.y),self.y).subs(r2,self.r**2)
72    self.ghess[7] = diff(diff(diff(gto_xyz,self.y),self.y),self.z).subs(r2,self.r**2)
73    self.ghess[8] = diff(diff(diff(gto_xyz,self.y),self.z),self.z).subs(r2,self.r**2)
74    self.ghess[9] = diff(diff(diff(gto_xyz,self.z),self.z),self.z).subs(r2,self.r**2)
75
76
77  def set_basis(self, basis):
78    self.basis = basis
79
80  def make_subs_list(self, i, j, k, x, y, z, alpha):
81      subs_list = {self.i:i, self.j:j, self.k:k}
82      r2 = x**2 + y**2 + z**2
83      r_val = sqrt(r2)
84      subs_list[self.r] = r_val
85      subs_list[self.r**2] = r2
86      subs_list[self.alpha] = alpha
87      subs_list[self.x] = x
88      subs_list[self.y] = y
89      subs_list[self.z] = z
90      return subs_list
91  def make_subs_ijk_list(self,i,j,k):
92      subs_list = {self.i:i, self.j:j, self.k:k}
93      return subs_list
94
95  def eval_single_v(self, i, j, k, x, y, z, alpha):
96      xc = x - self.center[0]
97      yc = y - self.center[1]
98      zc = z - self.center[2]
99      sl1 = self.make_subs_list(i,j,k,xc,yc,zc,alpha)
100      v = self.gto_sym.subs(sl1).evalf()
101      return v
102
103  def eval_single_vgl(self, i, j, k, x, y, z, alpha):
104      xc = x - self.center[0]
105      yc = y - self.center[1]
106      zc = z - self.center[2]
107      sl1 = self.make_subs_list(i,j,k,xc,yc,zc,alpha)
108      v = self.gto_sym.subs(sl1).evalf()
109      g = [grad.subs(sl1).evalf() for grad in self.grad]
110      lap = self.lap.subs(sl1).evalf()
111      return v,g,lap
112
113  def eval_single_vgh(self, i, j, k, x, y, z, alpha):
114      xc = x - self.center[0]
115      yc = y - self.center[1]
116      zc = z - self.center[2]
117      #This is to deal with the sympy "feature" encountered, explained below...
118      expsub=self.make_subs_ijk_list(i,j,k)
119      sl1 = self.make_subs_list(i,j,k,xc,yc,zc,alpha)
120      v = self.gto_sym.subs(sl1).evalf()
121      g = [grad.subs(sl1).evalf() for grad in self.grad]
122      #Since we are taking derivatives of x^i*y^j*z^k, derivaties of the GTO basis functions
123      #will reduce the exponents on the cartesian tensor terms.  Depending on how sympy
124      #tries to evaluate the terms, it can end up trying to evaluate things like y^(j-1). If
125      #j=0 and y=0; this will results in nan or inf, even though the properly evaluated term will have
126      #compensating terms to cancel out this divergence.
127      #
128      #Thus, we evaluate the expression with i,j,k specified.  Then we simplify this expression, allowing divergent terms
129      #to be taken care of.  Then we do the normal subs(sl1).evalf().
130      h = [simplify(hess.subs(expsub)).subs(sl1).evalf() for hess in self.hess]
131      return v,g,h
132
133  def eval_single_gradhess(self, i, j, k, x, y, z, alpha):
134      xc = x - self.center[0]
135      yc = y - self.center[1]
136      zc = z - self.center[2]
137      sl1 = self.make_subs_list(i,j,k,xc,yc,zc,alpha)
138      #see eval_single_vgh for why this trick is employed.
139      expsub=self.make_subs_ijk_list(i,j,k)
140      gh = [simplify(ghess.subs(expsub)).subs(sl1).evalf() for ghess in self.ghess]
141      return gh
142
143  def eval_contraction_v(self, x, y, z, basis):
144      vals = []
145      # Expand each basis type by the angular momentum state
146      angular_list = self.ijk[basis.orbtype]
147      for i,j,k,name in angular_list:
148        val = 0.0
149        for idx in range(basis.nbasis):
150          val += basis.contraction_coeff[idx] * self.eval_single_v(i,j,k,x,y,z,basis.zeta[idx])
151        vals.append(val)
152
153      return vals
154
155  def eval_contraction_vgl(self, x, y, z, basis):
156      vals = []
157      grads = []
158      laps = []
159
160      angular_list = self.ijk[basis.orbtype]
161      for i,j,k,name in angular_list:
162        val = 0.0
163        grad = [0.0, 0.0, 0.0]
164        lap = 0.0
165        for idx in range(basis.nbasis):
166          c = basis.contraction_coeff[idx]
167          v,g,l = self.eval_single_vgl(i,j,k,x,y,z,basis.zeta[idx])
168          val += c*v
169          lap += c*l
170          grad = [c*g[m] + grad[m] for m in range(3)]
171        vals.append(val)
172        grads.append(grad)
173        laps.append(lap)
174      return vals, grads, laps
175
176  def eval_contraction_vgh(self, x, y, z, basis):
177      vals = []
178      grads = []
179      hesses = []
180
181      angular_list = self.ijk[basis.orbtype]
182      for i,j,k,name in angular_list:
183        val = 0.0
184        grad = [0.0, 0.0, 0.0]
185        hess = [0.0,0.0,0.0,0.0,0.0,0.0]
186        for idx in range(basis.nbasis):
187          c = basis.contraction_coeff[idx]
188          v,g,h = self.eval_single_vgh(i,j,k,x,y,z,basis.zeta[idx])
189          val += c*v
190          grad = [c*g[m] + grad[m] for m in range(3)]
191          hess = [c*h[m] + hess[m] for m in range(6)]
192        vals.append(val)
193        grads.append(grad)
194        hesses.append(hess)
195      return vals, grads, hesses
196
197  def eval_contraction_gradhess(self, x, y, z, basis):
198      gradhesses = []
199      angular_list = self.ijk[basis.orbtype]
200      for i,j,k,name in angular_list:
201        ghess = [0.0 for m in range(10)]
202        for idx in range(basis.nbasis):
203          c = basis.contraction_coeff[idx]
204          gh = self.eval_single_gradhess(i,j,k,x,y,z,basis.zeta[idx])
205          ghess = [c*gh[m] + ghess[m] for m in range(10)]
206        gradhesses.append(ghess)
207      return gradhesses
208
209  def eval_v(self, x, y, z):
210    vs = []
211    for basis in self.basis:
212        v = self.eval_contraction_v(x, y, z, basis)
213        vs.extend(v)
214    return vs
215
216  def eval_vgl(self, x, y, z):
217    vs = []
218    grads = []
219    lapls = []
220    for basis in self.basis:
221        v,g,l = self.eval_contraction_vgl(x, y, z, basis)
222        vs.extend(v)
223        grads.extend(g)
224        lapls.extend(l)
225    return vs, grads, lapls
226
227  def eval_vgh(self, x, y, z):
228    vs = []
229    grads = []
230    hesses = []
231    for basis in self.basis:
232        v,g,hess = self.eval_contraction_vgh(x, y, z, basis)
233        vs.extend(v)
234        grads.extend(g)
235        hesses.extend(hess)
236    return vs, grads, hesses
237
238  def eval_gradhess(self,x,y,z):
239    gradhesses = []
240    for basis in self.basis:
241        ghess = self.eval_contraction_gradhess(x, y, z, basis)
242        gradhesses.extend(ghess)
243    return gradhesses
244
245# generated from qmcpack src/Numerics/codegen/read_order.py
246# Only part of the function included for now
247def get_ijk():
248  ijk = []
249  # S
250  ijk.append( (0,0,0,"S") )
251  # P
252  ijk.append( (1,0,0,"X") )
253  ijk.append( (0,1,0,"Y") )
254  ijk.append( (0,0,1,"Z") )
255  # D
256  ijk.append( (2,0,0,"XX") )
257  ijk.append( (0,2,0,"YY") )
258  ijk.append( (0,0,2,"ZZ") )
259  ijk.append( (1,1,0,"XY") )
260  ijk.append( (1,0,1,"XZ") )
261  ijk.append( (0,1,1,"YZ") )
262
263  return ijk
264
265def get_ijk_by_type():
266  ijk_list = get_ijk()
267
268  by_type = defaultdict(list)
269  for i,j,k,name in ijk_list:
270    L = i + j + k
271    by_type[L].append((i,j,k,name))
272
273  return by_type
274
275def get_ijk_inverse_index(basis_set):
276  ijk_list = get_ijk_by_type()
277  by_index = list()
278  for basis in basis_set:
279    angular_list = ijk_list[basis.orbtype]
280    for angular_info in angular_list:
281      by_index.append( (basis, angular_info) )
282  return by_index
283
284
285
286# Collection of atoms with different types and positions
287class GTO_centers:
288  def __init__(self, pos_list, elements, basis_sets):
289    self.gto_list = []
290    for pos, element in zip(pos_list, elements):
291      gto = GTO(basis_sets[element], pos)
292      self.gto_list.append(gto)
293
294  def eval_v(self, x, y, z):
295    vs = []
296    for gto in self.gto_list:
297      v = gto.eval_v(x, y, z)
298      vs.extend(v)
299    return vs
300
301  def eval_vgl(self, x, y, z):
302    vs = []
303    grads = []
304    laps = []
305    for gto in self.gto_list:
306      v,g,l = gto.eval_vgl(x,y,z)
307      vs.extend(v)
308      grads.extend(g)
309      laps.extend(l)
310
311    return vs, grads, laps
312
313  def eval_vgh(self, x, y, z):
314    vs = []
315    grads = []
316    hesses = []
317    for gto in self.gto_list:
318      v,g,h = gto.eval_vgh(x,y,z)
319      vs.extend(v)
320      grads.extend(g)
321      hesses.extend(h)
322
323    return vs, grads, hesses
324
325  def eval_gradhess(self, x, y, z):
326    ghesses = []
327    count=0
328    for gto in self.gto_list:
329      gh = gto.eval_gradhess(x,y,z)
330      ghesses.extend(gh)
331    return ghesses
332
333def get_center_and_ijk_by_index(pos_list, elements, basis_sets):
334  index = []
335  for pos_idx, (pos, element) in enumerate(zip(pos_list, elements)):
336    index_for_one = get_ijk_inverse_index(basis_sets[element])
337    for basis,angular_info in index_for_one:
338      index.append((pos_idx, basis, angular_info))
339  return index
340
341class MolecularOrbital:
342  def __init__(self, gto, MO_matrix):
343    self.gto = gto
344    self.MO_matrix = MO_matrix
345
346  def eval_v(self, x, y, z):
347    ao_vals = self.gto.eval_v(x, y, z)
348    mo_vals = np.dot(self.MO_matrix, ao_vals)
349    return mo_vals
350
351  def eval_v_one_MO(self, x, y, z, mo_idx):
352    ao_vals = self.gto.eval_v(x, y, z)
353    mo_val = np.dot(self.MO_matrix[mo_idx, :], ao_vals)
354    return mo_val
355
356  def eval_vgl(self, x, y, z):
357    ao_v, ao_g, ao_l = self.gto.eval_vgl(x, y, z)
358    mo_v = np.dot(self.MO_matrix, ao_v)
359    mo_g = np.dot(self.MO_matrix, ao_g)
360    mo_l = np.dot(self.MO_matrix, ao_l)
361
362    return mo_v, mo_g, mo_l
363
364  def eval_vgl_one_MO(self, x, y, z, mo_idx):
365    ao_vals, ao_g, ao_l = self.gto.eval_vgl(x, y, z)
366    mo_val = np.dot(self.MO_matrix[mo_idx, :], ao_vals)
367    mo_g = np.dot(self.MO_matrix[mo_idx, :], ao_g)
368    mo_l = np.dot(self.MO_matrix[mo_idx, :], ao_l)
369    return mo_val, mo_g, mo_l
370
371
372
373if __name__ == '__main__':
374  gto = GTO()
375