1 /*
2  *                This source code is part of
3  *
4  *                     E  R  K  A  L  E
5  *                             -
6  *                       DFT from Hel
7  *
8  * Written by Susi Lehtola, 2010-2012
9  * Copyright (c) 2010-2012, Susi Lehtola
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  */
16 
17 #include "emd_gto.h"
18 #include "../mathf.h"
19 #include <algorithm>
20 
RadialGaussian(int lambdav,int lv)21 RadialGaussian::RadialGaussian(int lambdav, int lv) : RadialFourier(lv) {
22   lambda=lambdav;
23 }
24 
~RadialGaussian()25 RadialGaussian::~RadialGaussian() {
26 }
27 
add_term(const contr_t & t)28 void RadialGaussian::add_term(const contr_t & t) {
29   if(c.size()==0) {
30     c.push_back(t);
31   } else {
32     // Get upper bound
33     std::vector<contr_t>::iterator high;
34     high=std::upper_bound(c.begin(),c.end(),t);
35 
36     // Corresponding index is
37     size_t ind=high-c.begin();
38 
39     if(ind>0 && c[ind-1].z==t.z)
40       // Found it.
41       c[ind-1].c+=t.c;
42     else {
43       // Term does not exist, add it
44       c.insert(high,t);
45     }
46   }
47 }
48 
print() const49 void RadialGaussian::print() const {
50   printf("l=%i, lambda=%i:",l,lambda);
51   for(size_t i=0;i<c.size();i++)
52     printf(" %+e (%e)\n",c[i].c,c[i].z);
53 }
54 
get(double p) const55 std::complex<double> RadialGaussian::get(double p) const {
56   std::complex<double> ret=0.0;
57 
58   if(lambda==l) {
59     // Pure spherical harmonics.
60     for(size_t i=0;i<c.size();i++)
61       ret+=c[i].c*exp(-p*p/(4.0*c[i].z));
62   } else {
63     // Mixed set.
64     for(size_t i=0;i<c.size();i++)
65       ret+=c[i].c*hyperg_1F1((l+lambda)/2.0+1.5,l+1.5,-p*p/(4.0*c[i].z));
66 
67     ret*=pow(sqrt(2.0),lambda-l)*doublefact(l+lambda+1)/doublefact(2*l+1);
68   }
69 
70   ret*=pow(std::complex<double>(0.0,-p),l)*pow(M_2_PI,1.0/4.0)/sqrt(doublefact(2*lambda+1));
71 
72   return ret;
73 }
74 
find_identical_functions(const BasisSet & bas)75 std::vector< std::vector<size_t> > find_identical_functions(const BasisSet & bas) {
76   // Get shells in basis set
77   std::vector<GaussianShell> sh=bas.get_shells();
78   // and the list of "identical" shells
79   std::vector< std::vector<size_t> > idsh=bas.find_identical_shells();
80 
81   // The returned list is
82   std::vector< std::vector<size_t> > ret;
83 
84   // Now we just add all of the functions to their separate lists.
85   for(size_t iidsh=0;iidsh<idsh.size();iidsh++) {
86     // The index of the first function in the return array is
87     size_t first=ret.size();
88 
89     // Increase the size of the return array
90     ret.resize(ret.size()+bas.get_Nbf(idsh[iidsh][0]));
91 
92     // Add the functions on all equivalent shells
93     for(size_t ifunc=0;ifunc<bas.get_Nbf(idsh[iidsh][0]);ifunc++)
94       for(size_t ish=0;ish<idsh[iidsh].size();ish++)
95 	ret[first+ifunc].push_back(bas.get_first_ind(idsh[iidsh][ish])+ifunc);
96   }
97 
98   /*
99   printf("Identical functions:\n");
100   for(size_t ig=0;ig<ret.size();ig++) {
101     printf("Group %i:",(int) ig);
102     for(size_t i=0;i<ret[ig].size();i++)
103       printf(" %i",(int) ret[ig][i]);
104     printf("\n");
105   }
106   */
107 
108   return ret;
109 }
110 
form_clm(const BasisSet & bas)111 std::vector< std::vector<ylmcoeff_t> > form_clm(const BasisSet & bas) {
112   // Get shells in basis set
113   std::vector<GaussianShell> sh=bas.get_shells();
114   // and the list of "identical" shells
115   std::vector< std::vector<size_t> > idsh=bas.find_identical_shells();
116 
117   // Returned decompositions
118   std::vector< std::vector<ylmcoeff_t> > ret;
119 
120   // Form cartesian expansions
121   CartesianExpansion cart(bas.get_max_am());
122 
123   // Loop over shells
124   for(size_t iid=0;iid<idsh.size();iid++) {
125     // Angular momentum
126     int l=bas.get_am(idsh[iid][0]);
127 
128     // Are spherical harmonics used?
129     if(bas.lm_in_use(idsh[iid][0])) {
130       // Easy job.
131       for(int m=-l;m<=l;m++) {
132 	// The coefficients for current m
133 	std::vector<ylmcoeff_t> c;
134 
135 	ylmcoeff_t tmp;
136 	tmp.l=l;
137 
138 	if(m==0) {
139 	  tmp.m=0;
140 	  tmp.c=1.0;
141 	  c.push_back(tmp);
142 	} else if(m>0) {
143 	  // Y_{lm} = ( (-1)^m Y_l^m + Y_l^{-m} ) / sqrt(2)
144 	  tmp.m=m;
145 	  tmp.c=M_SQRT1_2*pow(-1,m);
146 	  c.push_back(tmp);
147 
148 	  tmp.m=-m;
149 	  tmp.c=M_SQRT1_2;
150 	  c.push_back(tmp);
151 	} else {
152 	  // Y_{lm} = ( (-1)^m Y_l^{-m} - Y_l^{m} ) / [i sqrt(2)]
153 
154 	  tmp.m=-m;
155 	  tmp.c=std::complex<double>(0.0,-M_SQRT1_2*pow(-1,m));
156 	  c.push_back(tmp);
157 
158 	  tmp.m=m;
159 	  tmp.c=std::complex<double>(0.0,M_SQRT1_2);
160 	  c.push_back(tmp);
161 	}
162 
163 	// Add function to stack
164 	ret.push_back(c);
165       }
166     } else {
167       // Need to do cartesian decomposition. Loop over functions on shell.
168       for(int i=0; i<=l; i++) {
169 	int nx = l - i;
170 	for(int j=0; j<=i; j++) {
171 	  int ny = i-j;
172 	  int nz = j;
173 
174 	  // Get transform
175 	  SphericalExpansion expn=cart.get(nx,ny,nz);
176 
177 	  // Get coefficients
178 	  std::vector<ylmcoeff_t> c=expn.getcoeffs();
179 	  // and normalize them
180 	  double n=0.0;
181 	  for(size_t ic=0;ic<c.size();ic++)
182 	    n+=norm(c[ic].c);
183 	  n=sqrt(n);
184 	  for(size_t ic=0;ic<c.size();ic++)
185 	    c[ic].c/=n;
186 
187 	  // and add them to the stack
188 	  ret.push_back(c);
189 	}
190       }
191     }
192   }
193 
194   /*
195   for(size_t i=0;i<ret.size();i++) {
196     printf("*** Function %3i ***\n",(int) i +1);
197     for(size_t j=0;j<ret[i].size();j++)
198       printf(" (% e,% e) Y_%i^%+i",ret[i][j].c.real(),ret[i][j].c.imag(),ret[i][j].l,ret[i][j].m);
199     printf("\n");
200   }
201   */
202 
203   return ret;
204 }
205 
form_radial(const BasisSet & bas)206 std::vector< std::vector<RadialGaussian> > form_radial(const BasisSet & bas) {
207   // Get shells in basis set
208   std::vector<GaussianShell> sh=bas.get_shells();
209   // and the list of "identical" shells
210   std::vector< std::vector<size_t> > idsh=bas.find_identical_shells();
211 
212   // Returned functions
213   std::vector< std::vector<RadialGaussian> > ret;
214 
215   // Form cartesian expansions
216   CartesianExpansion cart(bas.get_max_am());
217 
218   // Loop over shells
219   for(size_t iid=0;iid<idsh.size();iid++) {
220     // Angular momentum
221     int am=bas.get_am(idsh[iid][0]);
222 
223     // Normalized contraction for shell
224     std::vector<contr_t> c=bas.get_contr_normalized(idsh[iid][0]);
225 
226     // The radial part for this shell
227     std::vector<RadialGaussian> rad;
228 
229     // Are spherical harmonics used?
230     if(bas.lm_in_use(idsh[iid][0])) {
231       // Yes. We only get a single l value.
232       RadialGaussian help(am,am);
233 
234       // Add the contractions.
235       for(size_t i=0;i<c.size();i++) {
236 	contr_t term;
237 	term.z=c[i].z;
238 	term.c=c[i].c*pow(c[i].z,-am/2.0-3.0/4.0);
239 	help.add_term(term);
240       }
241       rad.push_back(help);
242 
243       // All functions on shell have the same radial part
244       for(size_t ind=0;ind<bas.get_Nbf(idsh[iid][0]);ind++)
245 	ret.push_back(rad);
246 
247     } else {
248       // No, we get multiple values of l.
249 
250       // Loop over possible l values
251       for(int l=am;l>=0;l-=2) {
252 	// Construct the radial gaussian
253 	RadialGaussian help(am,l);
254 
255 	// Add the contractions.
256 	for(size_t i=0;i<c.size();i++) {
257 	  contr_t term;
258 	  term.z=c[i].z;
259 	  term.c=c[i].c*pow(c[i].z,-l/2.0-3.0/4.0);
260 	  help.add_term(term);
261 	}
262 	// and add the term
263 	rad.push_back(help);
264       }
265 
266       // All functions on shell have the same radial part
267       for(size_t ind=0;ind<bas.get_Nbf(idsh[iid][0]);ind++)
268 	ret.push_back(rad);
269     }
270   }
271 
272   return ret;
273 }
274 
GaussianEMDEvaluator()275 GaussianEMDEvaluator::GaussianEMDEvaluator() {
276 }
277 
GaussianEMDEvaluator(const BasisSet & bas,const arma::cx_mat & Pv,int lp,int mp)278 GaussianEMDEvaluator::GaussianEMDEvaluator(const BasisSet & bas, const arma::cx_mat & Pv, int lp, int mp) {
279   // Check size of P
280   if(Pv.n_cols!=Pv.n_rows) {
281     ERROR_INFO();
282     throw std::runtime_error("P is not square matrix!\n");
283   }
284   if(Pv.n_cols!=bas.get_Nbf()) {
285     ERROR_INFO();
286     throw std::runtime_error("Density matrix does not correspond to basis!\n");
287   }
288 
289   // Form radial functions
290   radf=form_radial(bas);
291 
292   // Form identical functions
293   std::vector< std::vector<size_t> > idf=find_identical_functions(bas);
294 
295   // Form Ylm expansion of functions
296   std::vector< std::vector<ylmcoeff_t> > clm=form_clm(bas);
297 
298   // Form the index list of the centers of the functions
299   std::vector<size_t> locv;
300   for(size_t ish=0;ish<bas.get_Nshells();ish++)
301     for(size_t ifunc=0;ifunc<bas.get_Nbf(ish);ifunc++)
302       locv.push_back(bas.get_shell_center_ind(ish));
303 
304   /*
305   printf("Functions centered on atoms:\n");
306   for(size_t i=0;i<loc.size();i++)
307     printf("%i: %i\n",(int) i+1, (int) loc[i]+1);
308   */
309 
310   // Form the list of atomic coordinates
311   std::vector<coords_t> coord;
312   for(size_t inuc=0;inuc<bas.get_Nnuc();inuc++)
313     coord.push_back(bas.get_nuclear_coords(inuc));
314 
315   /*
316   printf("Coordinates of atoms:\n");
317   for(size_t i=0;i<bas.get_Nnuc();i++)
318     printf("%3i % f % f % f\n",(int) i+1, coord[i].x, coord[i].y, coord[i].z);
319   */
320 
321   *this=GaussianEMDEvaluator(radf,idf,clm,locv,coord,Pv,lp,mp);
322 
323   // Check norm of radial functions
324   //  check_norm();
325 }
326 
GaussianEMDEvaluator(const std::vector<std::vector<RadialGaussian>> & radfv,const std::vector<std::vector<size_t>> & idfuncsv,const std::vector<std::vector<ylmcoeff_t>> & clm,const std::vector<size_t> & locv,const std::vector<coords_t> & coord,const arma::cx_mat & Pv,int lp,int mp)327 GaussianEMDEvaluator::GaussianEMDEvaluator(const std::vector< std::vector<RadialGaussian> > & radfv, const std::vector< std::vector<size_t> > & idfuncsv, const std::vector< std::vector<ylmcoeff_t> > & clm, const std::vector<size_t> & locv, const std::vector<coords_t> & coord, const arma::cx_mat & Pv, int lp, int mp) : EMDEvaluator(idfuncsv,clm,locv,coord,Pv,lp,mp) {
328   // Set the radial functions
329   radf=radfv;
330   // and assign the necessary pointers
331   update_pointers();
332 }
333 
~GaussianEMDEvaluator()334 GaussianEMDEvaluator::~GaussianEMDEvaluator() {
335 }
336 
337 
operator =(const GaussianEMDEvaluator & rhs)338 GaussianEMDEvaluator & GaussianEMDEvaluator::operator=(const GaussianEMDEvaluator & rhs) {
339   // Assign superclass part
340   EMDEvaluator::operator=(rhs);
341   // Copy radial functions
342   radf=rhs.radf;
343   // Update the pointers
344   update_pointers();
345 
346   return *this;
347 }
348 
update_pointers()349 void GaussianEMDEvaluator::update_pointers() {
350   rad.resize(radf.size());
351   for(size_t i=0;i<radf.size();i++) {
352     rad[i].resize(radf[i].size());
353     for(size_t j=0;j<radf[i].size();j++)
354       rad[i][j]=&radf[i][j];
355   }
356 }
357