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-2013
9 * Copyright (c) 2010-2013, 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
18
19 #include <algorithm>
20 #include <cfloat>
21 #include <cmath>
22 #include <cstdio>
23 #include <string>
24 // For exceptions
25 #include <sstream>
26 #include <stdexcept>
27
28 #include "basis.h"
29 #include "eriworker.h"
30 #include "elements.h"
31 #include "integrals.h"
32 #include "linalg.h"
33 #include "mathf.h"
34 #include "obara-saika.h"
35 #include "settings.h"
36 #include "solidharmonics.h"
37 #include "stringutil.h"
38
39 // Debug LIBINT routines against Huzinaga integrals?
40 //#define LIBINTDEBUG
41
42 // Derivative operator
_der1(const double x[],int l,double zeta)43 inline double _der1(const double x[], int l, double zeta) {
44 double d=-2.0*zeta*x[l+1];
45 if(l>0)
46 d+=l*x[l-1];
47 return d;
48 }
49
50 // Second derivative operator
_der2(const double x[],int l,double zeta)51 inline double _der2(const double x[], int l, double zeta) {
52 double d=4.0*zeta*zeta*x[l+2] - 2.0*zeta*(2*l+1)*x[l];
53 if(l>1)
54 d+=l*(l-1)*x[l-2];
55 return d;
56 }
57
58 // Third derivative operator
_der3(const double x[],int l,double zeta)59 inline double _der3(const double x[], int l, double zeta) {
60 double d=-8.0*zeta*zeta*zeta*x[l+3] + 12.0*zeta*zeta*(l+1)*x[l+1];
61 if(l>0)
62 d-=6.0*zeta*l*l*x[l-1];
63 if(l>2)
64 d+=l*(l-1)*(l-2)*x[l-3];
65 return d;
66 }
67
operator ==(const nucleus_t & lhs,const nucleus_t & rhs)68 bool operator==(const nucleus_t & lhs, const nucleus_t & rhs) {
69 return (lhs.ind == rhs.ind) && (lhs.r == rhs.r) && (lhs.Z == rhs.Z) && \
70 (lhs.bsse == rhs.bsse) && (stricmp(lhs.symbol,rhs.symbol)==0);
71 }
72
operator ==(const coords_t & lhs,const coords_t & rhs)73 bool operator==(const coords_t & lhs, const coords_t & rhs) {
74 return (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z);
75 }
76
coords_to_vec(const coords_t & c)77 arma::vec coords_to_vec(const coords_t & c) {
78 arma::vec r(3);
79 r(0)=c.x;
80 r(1)=c.y;
81 r(2)=c.z;
82 return r;
83 }
84
vec_to_coords(const arma::vec & v)85 coords_t vec_to_coords(const arma::vec & v) {
86 if(v.n_elem != 3) {
87 std::ostringstream oss;
88 oss << "Expected a 3-element vector, got " << v.n_elem << "!\n";
89 throw std::logic_error(oss.str());
90 }
91 coords_t r;
92 r.x=v(0);
93 r.y=v(1);
94 r.z=v(2);
95 return r;
96 }
97
98 // Operators for computing displacements
operator -(const coords_t & lhs,const coords_t & rhs)99 coords_t operator-(const coords_t & lhs, const coords_t & rhs) {
100 coords_t ret;
101 ret.x=lhs.x-rhs.x;
102 ret.y=lhs.y-rhs.y;
103 ret.z=lhs.z-rhs.z;
104 return ret;
105 }
106
operator +(const coords_t & lhs,const coords_t & rhs)107 coords_t operator+(const coords_t & lhs, const coords_t& rhs) {
108 coords_t ret;
109 ret.x=lhs.x+rhs.x;
110 ret.y=lhs.y+rhs.y;
111 ret.z=lhs.z+rhs.z;
112 return ret;
113 }
114
operator /(const coords_t & lhs,double fac)115 coords_t operator/(const coords_t & lhs, double fac) {
116 coords_t ret;
117 ret.x=lhs.x/fac;
118 ret.y=lhs.y/fac;
119 ret.z=lhs.z/fac;
120 return ret;
121 }
122
operator *(const coords_t & lhs,double fac)123 coords_t operator*(const coords_t & lhs, double fac) {
124 coords_t ret;
125 ret.x=lhs.x*fac;
126 ret.y=lhs.y*fac;
127 ret.z=lhs.z*fac;
128 return ret;
129 }
130
operator <(const contr_t & lhs,const contr_t & rhs)131 bool operator<(const contr_t & lhs, const contr_t & rhs) {
132 // Decreasing order of exponents.
133 return lhs.z>rhs.z;
134 }
135
operator ==(const contr_t & lhs,const contr_t & rhs)136 bool operator==(const contr_t & lhs, const contr_t & rhs) {
137 // return (lhs.z==rhs.z) && (lhs.c==rhs.c);
138
139 // Since this also needs to work for saved and reloaded basis sets, we need to relax the comparison.
140 const double tol=sqrt(DBL_EPSILON);
141
142 bool same=(fabs(lhs.z-rhs.z)<tol*std::max(1.0,fabs(lhs.z))) && (fabs(lhs.c-rhs.c)<tol*std::max(1.0,fabs(lhs.z)));
143
144 /*
145 if(!same) {
146 fprintf(stderr,"Contractions differ: %e %e vs %e %e, diff %e %e!\n",lhs.c,lhs.z,rhs.c,rhs.z,rhs.c-lhs.c,rhs.z-lhs.z);
147 }
148 */
149
150 return same;
151 }
152
GaussianShell()153 GaussianShell::GaussianShell() {
154 // Dummy constructor
155 }
156
GaussianShell(int amv,bool lm,const std::vector<contr_t> & C)157 GaussianShell::GaussianShell(int amv, bool lm, const std::vector<contr_t> & C) {
158 // Construct shell of basis functions
159
160 // Store contraction
161 c=C;
162 // Sort the contraction
163 sort();
164
165 // Set angular momentum
166 am=amv;
167 // Use spherical harmonics?
168 uselm=lm;
169
170 // If spherical harmonics are used, fill transformation matrix
171 if(uselm)
172 transmat=Ylm_transmat(am);
173 else {
174 // Do away with uninitialized value warnings in valgrind
175 transmat=arma::mat(1,1);
176 transmat(0,0)=1.0/0.0; // Initialize to NaN
177 }
178
179 // Compute necessary amount of Cartesians
180 size_t Ncart=(am+1)*(am+2)/2;
181 // Allocate memory
182 cart.reserve(Ncart);
183 cart.resize(Ncart);
184 // Initialize the shells
185
186 int n=0;
187 for(int i=0; i<=am; i++) {
188 int nx = am - i;
189 for(int j=0; j<=i; j++) {
190 int ny = i-j;
191 int nz = j;
192
193 cart[n].l=nx;
194 cart[n].m=ny;
195 cart[n].n=nz;
196 cart[n].relnorm=1.0;
197 n++;
198 }
199 }
200
201 // Default values
202 indstart=0;
203 cenind=0;
204 cen.x=cen.y=cen.z=0.0;
205 }
206
~GaussianShell()207 GaussianShell::~GaussianShell() {
208 }
209
set_first_ind(size_t ind)210 void GaussianShell::set_first_ind(size_t ind) {
211 indstart=ind;
212 }
213
set_center(const coords_t & cenv,size_t cenindv)214 void GaussianShell::set_center(const coords_t & cenv, size_t cenindv) {
215 cen=cenv;
216 cenind=cenindv;
217 }
218
sort()219 void GaussianShell::sort() {
220 std::stable_sort(c.begin(),c.end());
221 }
222
convert_contraction()223 void GaussianShell::convert_contraction() {
224 // Convert contraction from contraction of normalized gaussians to
225 // contraction of unnormalized gaussians.
226
227 // Note - these refer to cartesian functions!
228 double fac=pow(M_2_PI,0.75)*pow(2,am)/sqrt(doublefact(2*am-1));
229
230 for(size_t i=0;i<c.size();i++)
231 c[i].c*=fac*pow(c[i].z,am/2.0+0.75);
232 }
233
convert_sap_contraction()234 void GaussianShell::convert_sap_contraction() {
235 // Convert contraction from contraction of normalized density
236 // gaussians to contraction of unnormalized gaussians.
237
238 if(am != 0) throw std::logic_error("SAP basis should only have S functions!\n");
239 for(size_t i=0;i<c.size();i++)
240 c[i].c*=pow(c[i].z/M_PI,1.5);
241 }
242
normalize(bool coeffs)243 void GaussianShell::normalize(bool coeffs) {
244 // Normalize contraction of unnormalized primitives wrt first function on shell
245
246 // Check for dummy shell
247 if(c.size()==1 && c[0].z==0.0) {
248 // Yes, this is a dummy.
249 c[0].c=1.0;
250 return;
251 }
252
253 if(coeffs) {
254 double fact=0.0;
255
256 // Calculate overlap of exponents
257 for(size_t i=0;i<c.size();i++)
258 for(size_t j=0;j<c.size();j++)
259 fact+=c[i].c*c[j].c/pow(c[i].z+c[j].z,am+1.5);
260
261 // Add constant part
262 fact*=pow(M_PI,1.5)*doublefact(2*am-1)/pow(2.0,am);
263
264 // The coefficients must be scaled by 1/sqrt(fact)
265 fact=1.0/sqrt(fact);
266 for(size_t i=0;i<c.size();i++)
267 c[i].c*=fact;
268 }
269
270 // FIXME: Do something more clever here.
271 if(!uselm) {
272 // Compute relative normalization factors
273 for(size_t i=0;i<cart.size();i++)
274 cart[i].relnorm=sqrt(doublefact(2*am-1)/(doublefact(2*cart[i].l-1)*doublefact(2*cart[i].m-1)*doublefact(2*cart[i].n-1)));
275 } else {
276 // Compute self-overlap
277 arma::mat S=overlap(*this);
278 // and scale coefficients.
279 for(size_t i=0;i<cart.size();i++)
280 cart[i].relnorm/=sqrt(S(0,0));
281 }
282 }
283
coulomb_normalize()284 void GaussianShell::coulomb_normalize() {
285 // Normalize functions using Coulomb norm
286 size_t Ncart=cart.size();
287 size_t Nbf=get_Nbf();
288
289 // Dummy shell
290 GaussianShell dummy;
291 dummy=dummyshell();
292
293 // Compute ERI
294 ERIWorker eri(get_am(),get_Ncontr());
295 eri.compute(this,&dummy,this,&dummy);
296 const std::vector<double> * erip=eri.getp();
297
298 if(!uselm) {
299 // Cartesian functions
300 for(size_t i=0;i<Ncart;i++)
301 cart[i].relnorm*=1.0/sqrt((*erip)[i*Nbf+i]);
302 } else {
303 // Spherical normalization, need to distribute
304 // normalization coefficient among cartesians
305
306 // Spherical ERI is
307 // ERI = transmat * ERI_cart * trans(transmat)
308
309 // FIXME - Do something more clever here
310 // Check that all factors are the same
311 int diff=0;
312 for(size_t i=1;i<Nbf;i++)
313 if(fabs((*erip)[i*Nbf+i]-(*erip)[0])>1000*DBL_EPSILON*(*erip)[0]) {
314 printf("%e != %e, diff %e\n",(*erip)[i*Nbf+i],(*erip)[0],(*erip)[i*Nbf+i]-(*erip)[0]);
315 diff++;
316 }
317
318 if(diff) {
319 ERROR_INFO();
320 std::ostringstream oss;
321 oss << "\nSpherical functions have different norms!\n";
322 throw std::runtime_error(oss.str());
323 }
324
325 // Scale coefficients
326 for(size_t i=0;i<Ncart;i++)
327 cart[i].relnorm*=1.0/sqrt((*erip)[0]);
328 }
329 }
330
get_contr() const331 std::vector<contr_t> GaussianShell::get_contr() const {
332 return c;
333 }
334
get_cart() const335 std::vector<shellf_t> GaussianShell::get_cart() const {
336 return cart;
337 }
338
get_contr_normalized() const339 std::vector<contr_t> GaussianShell::get_contr_normalized() const {
340 // Returned array
341 std::vector<contr_t> cn(c);
342
343 // Note - these refer to cartesian functions!
344 double fac=pow(M_2_PI,0.75)*pow(2,am)/sqrt(doublefact(2*am-1));
345
346 // Convert coefficients to those of normalized primitives
347 for(size_t i=0;i<cn.size();i++)
348 cn[i].c/=fac*pow(cn[i].z,am/2.0+0.75);
349
350 return cn;
351 }
352
get_Nbf() const353 size_t GaussianShell::get_Nbf() const {
354 if(uselm)
355 return get_Nlm();
356 else
357 return get_Ncart();
358 }
359
get_Nlm() const360 size_t GaussianShell::get_Nlm() const {
361 return 2*am+1;
362 }
363
range(double eps) const364 double GaussianShell::range(double eps) const {
365 double oldr;
366 // Start at
367 double r=1.0;
368
369 double val;
370 // Increase r so that value certainly has dropped below.
371 do {
372 // Increase value of r.
373 oldr=r;
374 r*=2.0;
375
376 val=0.0;
377 for(size_t i=0;i<c.size();i++)
378 val+=c[i].c*exp(-c[i].z*r*r);
379 val*=pow(r,am);
380 } while(fabs(val)>eps);
381
382 // OK, now the range lies in the range [oldr,r]. Use binary search to refine
383 double left=oldr, right=r;
384 double middle=(left+right)/2.0;
385
386 while(right-left>10*DBL_EPSILON*right) {
387 // Compute middle of interval
388 middle=(left+right)/2.0;
389
390 // Compute value in the middle
391 val=0.0;
392 for(size_t i=0;i<c.size();i++)
393 val+=c[i].c*exp(-c[i].z*middle*middle);
394 val*=pow(middle,am);
395
396 // Switch values
397 if(fabs(val)<eps) {
398 // Switch right value
399 right=middle;
400 } else
401 // Switch left value
402 left=middle;
403 }
404
405 return middle;
406 }
407
lm_in_use() const408 bool GaussianShell::lm_in_use() const {
409 return uselm;
410 }
411
set_lm(bool lm)412 void GaussianShell::set_lm(bool lm) {
413 uselm=lm;
414
415 if(uselm)
416 transmat=Ylm_transmat(am);
417 else
418 transmat=arma::mat();
419 }
420
get_trans() const421 arma::mat GaussianShell::get_trans() const {
422 return transmat;
423 }
424
get_Ncart() const425 size_t GaussianShell::get_Ncart() const {
426 return cart.size();
427 }
428
get_Ncontr() const429 size_t GaussianShell::get_Ncontr() const {
430 return c.size();
431 }
432
get_am() const433 int GaussianShell::get_am() const {
434 return am;
435 }
436
get_center_ind() const437 size_t GaussianShell::get_center_ind() const {
438 // return cen->ind;
439 return cenind;
440 }
441
get_center() const442 coords_t GaussianShell::get_center() const {
443 return cen;
444 }
445
operator <(const GaussianShell & rhs) const446 bool GaussianShell::operator<(const GaussianShell & rhs) const {
447 // Sort first by nucleus
448 if(cenind < rhs.cenind)
449 return true;
450 else if(cenind == rhs.cenind) {
451 // Then by angular momentum
452 if(am<rhs.am)
453 return true;
454 else if(am==rhs.am) {
455 // Then by decreasing order of exponents
456 if(c.size() && rhs.c.size())
457 return c[0].z>rhs.c[0].z;
458 }
459 }
460
461 return false;
462 }
463
464
operator ==(const GaussianShell & rhs) const465 bool GaussianShell::operator==(const GaussianShell & rhs) const {
466 // Check first nucleus
467 if(cenind != rhs.cenind) {
468 // fprintf(stderr,"Center indices differ!\n");
469 return false;
470 }
471
472 // Then, angular momentum
473 if(am!=rhs.am) {
474 // fprintf(stderr,"Angular momentum differs!\n");
475 return false;
476 }
477
478 // Then, by exponents
479 if(c.size() != rhs.c.size()) {
480 // fprintf(stderr,"Contraction size differs!\n");
481 return false;
482 }
483
484 for(size_t i=0;i<c.size();i++) {
485 if(!(c[i]==rhs.c[i])) {
486 // fprintf(stderr,"%i:th contraction differs!\n",(int) i+1);
487 return false;
488 }
489 }
490
491 return true;
492 }
493
get_first_ind() const494 size_t GaussianShell::get_first_ind() const {
495 return indstart;
496 }
497
get_last_ind() const498 size_t GaussianShell::get_last_ind() const {
499 return indstart+get_Nbf()-1;
500 }
501
print() const502 void GaussianShell::print() const {
503
504 printf("\t%c shell at nucleus %3i with with basis functions %4i-%-4i\n",shell_types[am],(int) (get_center_ind()+1),(int) get_first_ind()+1,(int) get_last_ind()+1);
505 printf("\t\tCenter of shell is at % 0.4f % 0.4f % 0.4f Å.\n",cen.x/ANGSTROMINBOHR,cen.y/ANGSTROMINBOHR,cen.z/ANGSTROMINBOHR);
506
507 // Get contraction of normalized primitives
508 std::vector<contr_t> cn(get_contr_normalized());
509
510 printf("\t\tExponential contraction is\n");
511 printf("\t\t\tzeta\t\tprimitive coeff\ttotal coeff\n");
512 for(size_t i=0;i<c.size();i++)
513 printf("\t\t\t%e\t% e\t% e\n",c[i].z,cn[i].c,c[i].c);
514 if(uselm) {
515 printf("\t\tThe functions on this shell are:\n\t\t\t");
516 for(int m=-am;m<=am;m++)
517 printf(" (%i,%i)",am,m);
518 printf("\n");
519 } else {
520 printf("\t\tThe functions on this shell are:\n\t\t\t");
521 for(size_t i=0;i<cart.size();i++) {
522 printf(" ");
523 if(cart[i].l+cart[i].m+cart[i].n==0)
524 printf("1");
525 else {
526 for(int j=0;j<cart[i].l;j++)
527 printf("x");
528 for(int j=0;j<cart[i].m;j++)
529 printf("y");
530 for(int j=0;j<cart[i].n;j++)
531 printf("z");
532 }
533 }
534 printf("\n");
535 }
536
537 /*
538 printf("\t\tThe cartesian functions on this shell are:\n");
539 for(size_t i=0;i<cart.size();i++)
540 printf("\t\t\t%i %i %i\t%0.6f\n",cart[i].l,cart[i].m,cart[i].n,cart[i].relnorm);
541 */
542 }
543
eval_func(double x,double y,double z) const544 arma::vec GaussianShell::eval_func(double x, double y, double z) const {
545 // Evaluate basis functions at (x,y,z)
546
547 // Compute coordinates relative to center
548 double xrel=x-cen.x;
549 double yrel=y-cen.y;
550 double zrel=z-cen.z;
551
552 double rrelsq=xrel*xrel+yrel*yrel+zrel*zrel;
553
554 // Evaluate exponential factor
555 double expfac=0;
556 for(size_t i=0;i<c.size();i++)
557 expfac+=c[i].c*exp(-c[i].z*rrelsq);
558
559 // Power arrays, x^l, y^l, z^l
560 double xr[am+1], yr[am+1], zr[am+1];
561
562 xr[0]=1.0;
563 yr[0]=1.0;
564 zr[0]=1.0;
565
566 if(am) {
567 xr[1]=xrel;
568 yr[1]=yrel;
569 zr[1]=zrel;
570
571 for(int i=2;i<=am;i++) {
572 xr[i]=xr[i-1]*xrel;
573 yr[i]=yr[i-1]*yrel;
574 zr[i]=zr[i-1]*zrel;
575 }
576 }
577
578 // Values of functions
579 arma::vec ret(cart.size());
580
581 // Loop over functions
582 for(size_t i=0;i<cart.size();i++) {
583 // Value of function at (x,y,z) is
584 ret[i]=cart[i].relnorm*xr[cart[i].l]*yr[cart[i].m]*zr[cart[i].n]*expfac;
585 }
586
587 if(uselm)
588 // Transform into spherical harmonics
589 return transmat*ret;
590 else
591 return ret;
592 }
593
eval_grad(double x,double y,double z) const594 arma::mat GaussianShell::eval_grad(double x, double y, double z) const {
595 // Evaluate gradients of functions at (x,y,z)
596
597 // Compute coordinates relative to center
598 double xrel=x-cen.x;
599 double yrel=y-cen.y;
600 double zrel=z-cen.z;
601
602 double rrelsq=xrel*xrel+yrel*yrel+zrel*zrel;
603
604 // Power arrays, x^l, y^l, z^l
605 double xr[am+2], yr[am+2], zr[am+2];
606
607 xr[0]=1.0;
608 yr[0]=1.0;
609 zr[0]=1.0;
610
611 xr[1]=xrel;
612 yr[1]=yrel;
613 zr[1]=zrel;
614
615 for(int i=2;i<=am+1;i++) {
616 xr[i]=xr[i-1]*xrel;
617 yr[i]=yr[i-1]*yrel;
618 zr[i]=zr[i-1]*zrel;
619 }
620
621 // Gradient array, N_cart x 3
622 arma::mat ret(cart.size(),3);
623 ret.zeros();
624
625 // Helper variables
626 double expf;
627
628 // Loop over functions
629 for(size_t icart=0;icart<cart.size();icart++) {
630 // Get types
631 int l=cart[icart].l;
632 int m=cart[icart].m;
633 int n=cart[icart].n;
634
635 // Loop over exponential contraction
636 for(size_t iexp=0;iexp<c.size();iexp++) {
637 // Contracted exponent
638 expf=c[iexp].c*exp(-c[iexp].z*rrelsq);
639
640 // x component of gradient:
641 ret(icart,0)+=_der1(xr,l,c[iexp].z)*yr[m]*zr[n]*expf;
642 // y component
643 ret(icart,1)+=xr[l]*_der1(yr,m,c[iexp].z)*zr[n]*expf;
644 // z component
645 ret(icart,2)+=xr[l]*yr[m]*_der1(zr,n,c[iexp].z)*expf;
646 }
647
648 // Plug in normalization constant
649 ret(icart,0)*=cart[icart].relnorm;
650 ret(icart,1)*=cart[icart].relnorm;
651 ret(icart,2)*=cart[icart].relnorm;
652 }
653
654 if(uselm)
655 // Need to transform into spherical harmonics
656 return transmat*ret;
657 else
658 return ret;
659 }
660
eval_lapl(double x,double y,double z) const661 arma::vec GaussianShell::eval_lapl(double x, double y, double z) const {
662 // Evaluate laplacian of basis functions at (x,y,z)
663
664 // Compute coordinates relative to center
665 double xrel=x-cen.x;
666 double yrel=y-cen.y;
667 double zrel=z-cen.z;
668
669 double rrelsq=xrel*xrel+yrel*yrel+zrel*zrel;
670
671 // Power arrays, x^l, y^l, z^l
672 double xr[am+3], yr[am+3], zr[am+3];
673
674 xr[0]=1.0;
675 yr[0]=1.0;
676 zr[0]=1.0;
677
678 xr[1]=xrel;
679 yr[1]=yrel;
680 zr[1]=zrel;
681
682 for(int i=2;i<=am+2;i++) {
683 xr[i]=xr[i-1]*xrel;
684 yr[i]=yr[i-1]*yrel;
685 zr[i]=zr[i-1]*zrel;
686 }
687
688 // Values of laplacians of functions
689 arma::vec ret(cart.size());
690 ret.zeros();
691
692 // Helper variables
693 double expf;
694
695 // Loop over functions
696 for(size_t icart=0;icart<cart.size();icart++) {
697 // Get types
698 int l=cart[icart].l;
699 int m=cart[icart].m;
700 int n=cart[icart].n;
701
702 // Loop over exponential contraction
703 for(size_t iexp=0;iexp<c.size();iexp++) {
704 // Contracted exponent
705 expf=c[iexp].c*exp(-c[iexp].z*rrelsq);
706
707 // Derivative wrt x
708 ret(icart)+=_der2(xr,l,c[iexp].z)*yr[m]*zr[n]*expf;
709 // Derivative wrt y
710 ret(icart)+=xr[l]*_der2(yr,m,c[iexp].z)*zr[n]*expf;
711 // Derivative wrt z
712 ret(icart)+=xr[l]*yr[m]*_der2(zr,n,c[iexp].z)*expf;
713 }
714
715 // Plug in normalization constant
716 ret(icart)*=cart[icart].relnorm;
717 }
718
719 if(uselm)
720 // Transform into spherical harmonics
721 return transmat*ret;
722 else
723 return ret;
724 }
725
eval_hess(double x,double y,double z) const726 arma::mat GaussianShell::eval_hess(double x, double y, double z) const {
727 // Evaluate gradients of functions at (x,y,z)
728
729 // Compute coordinates relative to center
730 double xrel=x-cen.x;
731 double yrel=y-cen.y;
732 double zrel=z-cen.z;
733
734 double rrelsq=xrel*xrel+yrel*yrel+zrel*zrel;
735
736 // Power arrays, x^l, y^l, z^l
737 double xr[am+3], yr[am+3], zr[am+3];
738
739 xr[0]=1.0;
740 yr[0]=1.0;
741 zr[0]=1.0;
742
743 xr[1]=xrel;
744 yr[1]=yrel;
745 zr[1]=zrel;
746
747 for(int i=2;i<=am+2;i++) {
748 xr[i]=xr[i-1]*xrel;
749 yr[i]=yr[i-1]*yrel;
750 zr[i]=zr[i-1]*zrel;
751 }
752
753 // Gradient array, Ncart x 9
754 arma::mat ret(cart.size(),9);
755 ret.zeros();
756
757 // Helper variables
758 double expf, tmp;
759
760 // Loop over functions
761 for(size_t icart=0;icart<cart.size();icart++) {
762 // Get types
763 int l=cart[icart].l;
764 int m=cart[icart].m;
765 int n=cart[icart].n;
766
767 // Loop over exponential contraction
768 for(size_t iexp=0;iexp<c.size();iexp++) {
769 // Contracted exponent
770 expf=c[iexp].c*exp(-c[iexp].z*rrelsq);
771
772 // d/dx^2
773 ret(icart,0)+=_der2(xr,l,c[iexp].z)*yr[m]*zr[n]*expf;
774 // d/dy^2
775 ret(icart,4)+=xr[l]*_der2(yr,m,c[iexp].z)*zr[n]*expf;
776 // d/dz^2
777 ret(icart,8)+=xr[l]*yr[m]*_der2(zr,n,c[iexp].z)*expf;
778
779 // dxdy = dydx
780 tmp=_der1(xr,l,c[iexp].z)*_der1(yr,m,c[iexp].z)*zr[n]*expf;
781 ret(icart,1)+=tmp;
782 ret(icart,3)+=tmp;
783
784 // dxdz = dzdx
785 tmp=_der1(xr,l,c[iexp].z)*yr[m]*_der1(zr,n,c[iexp].z)*expf;
786 ret(icart,2)+=tmp;
787 ret(icart,6)+=tmp;
788
789 // dydz = dzdy
790 tmp=xr[l]*_der1(yr,m,c[iexp].z)*_der1(zr,n,c[iexp].z)*expf;
791 ret(icart,5)+=tmp;
792 ret(icart,7)+=tmp;
793 }
794
795 // Plug in normalization constant
796 for(int ii=0;ii<9;ii++)
797 ret(icart,ii)*=cart[icart].relnorm;
798 }
799
800 if(uselm)
801 return transmat*ret;
802 else
803 return ret;
804 }
805
eval_laplgrad(double x,double y,double z) const806 arma::mat GaussianShell::eval_laplgrad(double x, double y, double z) const {
807 // Evaluate laplacian of gradients of functions at (x,y,z)
808
809 // Compute coordinates relative to center
810 double xrel=x-cen.x;
811 double yrel=y-cen.y;
812 double zrel=z-cen.z;
813
814 double rrelsq=xrel*xrel+yrel*yrel+zrel*zrel;
815
816 // Power arrays, x^l, y^l, z^l
817 double xr[am+4], yr[am+4], zr[am+4];
818
819 xr[0]=1.0;
820 yr[0]=1.0;
821 zr[0]=1.0;
822
823 xr[1]=xrel;
824 yr[1]=yrel;
825 zr[1]=zrel;
826
827 for(int i=2;i<=am+3;i++) {
828 xr[i]=xr[i-1]*xrel;
829 yr[i]=yr[i-1]*yrel;
830 zr[i]=zr[i-1]*zrel;
831 }
832
833 // Returned array, N_cart x 3
834 arma::mat ret(cart.size(),3);
835 ret.zeros();
836
837 // Helper variables
838 double expf;
839
840 // Loop over functions
841 for(size_t icart=0;icart<cart.size();icart++) {
842 // Get types
843 int l=cart[icart].l;
844 int m=cart[icart].m;
845 int n=cart[icart].n;
846
847 // Loop over exponential contraction
848 for(size_t iexp=0;iexp<c.size();iexp++) {
849 // Contracted exponent
850 expf=c[iexp].c*exp(-c[iexp].z*rrelsq);
851
852 // dx^3
853 ret(icart,0)+=_der3(xr,l,c[iexp].z)*yr[m]*zr[n]*expf;
854 // dy^2 dx
855 ret(icart,0)+=_der1(xr,l,c[iexp].z)*_der2(yr,m,c[iexp].z)*zr[n]*expf;
856 // dz^2 dx
857 ret(icart,0)+=_der1(xr,l,c[iexp].z)*yr[m]*_der2(zr,n,c[iexp].z)*expf;
858
859 // dx^2 dy
860 ret(icart,1)+=_der2(xr,l,c[iexp].z)*_der1(yr,m,c[iexp].z)*zr[n]*expf;
861 // dy^3
862 ret(icart,1)+=xr[l]*_der3(yr,m,c[iexp].z)*zr[n]*expf;
863 // dz^2 dy
864 ret(icart,1)+=xr[l]*_der1(yr,m,c[iexp].z)*_der2(zr,n,c[iexp].z)*expf;
865
866 // dx^2 dz
867 ret(icart,2)+=_der2(xr,l,c[iexp].z)*yr[m]*_der1(zr,n,c[iexp].z)*expf;
868 // dy^2 dz
869 ret(icart,2)+=xr[l]*_der2(yr,m,c[iexp].z)*_der1(zr,n,c[iexp].z)*expf;
870 // dz^3
871 ret(icart,2)+=xr[l]*yr[m]*_der3(zr,n,c[iexp].z)*expf;
872 }
873
874 // Plug in normalization constant
875 ret(icart,0)*=cart[icart].relnorm;
876 ret(icart,1)*=cart[icart].relnorm;
877 ret(icart,2)*=cart[icart].relnorm;
878 }
879
880 if(uselm)
881 // Need to transform into spherical harmonics
882 return transmat*ret;
883 else
884 return ret;
885 }
886
887 // Calculate overlaps between basis functions
overlap(const GaussianShell & rhs) const888 arma::mat GaussianShell::overlap(const GaussianShell & rhs) const {
889
890 // Overlap matrix
891 arma::mat S(cart.size(),rhs.cart.size());
892 S.zeros();
893
894 // Coordinates
895 double xa=cen.x;
896 double ya=cen.y;
897 double za=cen.z;
898
899 double xb=rhs.cen.x;
900 double yb=rhs.cen.y;
901 double zb=rhs.cen.z;
902
903 for(size_t ixl=0;ixl<c.size();ixl++)
904 for(size_t ixr=0;ixr<rhs.c.size();ixr++)
905 S+=c[ixl].c*rhs.c[ixr].c*overlap_int_os(xa,ya,za,c[ixl].z,cart,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
906
907 // Transformation to spherical harmonics. Left side:
908 if(uselm) {
909 S=transmat*S;
910 }
911 // Right side
912 if(rhs.uselm) {
913 S=S*arma::trans(rhs.transmat);
914 }
915
916 return S;
917 }
918
919 // Calculate overlaps between basis functions
coulomb_overlap(const GaussianShell & rhs) const920 arma::mat GaussianShell::coulomb_overlap(const GaussianShell & rhs) const {
921
922 // Number of functions on shells
923 size_t Ni=get_Nbf();
924 size_t Nj=rhs.get_Nbf();
925
926 // Compute ERI
927 GaussianShell dummy=dummyshell();
928 int maxam=std::max(get_am(),rhs.get_am());
929 int maxcontr=std::max(get_Ncontr(),rhs.get_Ncontr());
930 ERIWorker eri(maxam,maxcontr);
931 const std::vector<double> * erip;
932 eri.compute(this,&dummy,&rhs,&dummy);
933 erip=eri.getp();
934
935 // Fill overlap matrix
936 arma::mat S(Ni,Nj);
937 for(size_t i=0;i<Ni;i++)
938 for(size_t j=0;j<Nj;j++)
939 S(i,j)=(*erip)[i*Nj+j];
940
941 return S;
942 }
943
944 // Calculate kinetic energy matrix element between basis functions
kinetic(const GaussianShell & rhs) const945 arma::mat GaussianShell::kinetic(const GaussianShell & rhs) const {
946
947 // Kinetic energy matrix
948 arma::mat T(cart.size(),rhs.cart.size());
949 T.zeros();
950
951 // Coordinates
952 double xa=cen.x;
953 double ya=cen.y;
954 double za=cen.z;
955
956 double xb=rhs.cen.x;
957 double yb=rhs.cen.y;
958 double zb=rhs.cen.z;
959
960 for(size_t ixl=0;ixl<c.size();ixl++)
961 for(size_t ixr=0;ixr<rhs.c.size();ixr++)
962 T+=c[ixl].c*rhs.c[ixr].c*kinetic_int_os(xa,ya,za,c[ixl].z,cart,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
963
964 // Transformation to spherical harmonics. Left side:
965 if(uselm) {
966 T=transmat*T;
967 }
968 // Right side
969 if(rhs.uselm) {
970 T=T*arma::trans(rhs.transmat);
971 }
972
973 return T;
974 }
975
976
977 // Calculate nuclear attraction matrix element between basis functions
nuclear(double cx,double cy,double cz,const GaussianShell & rhs) const978 arma::mat GaussianShell::nuclear(double cx, double cy, double cz, const GaussianShell & rhs) const {
979
980 // Matrix element of nuclear attraction operator
981 arma::mat Vnuc(cart.size(),rhs.cart.size());
982 Vnuc.zeros();
983
984 // Coordinates
985 double xa=cen.x;
986 double ya=cen.y;
987 double za=cen.z;
988
989 double xb=rhs.cen.x;
990 double yb=rhs.cen.y;
991 double zb=rhs.cen.z;
992
993 for(size_t ixl=0;ixl<c.size();ixl++)
994 for(size_t ixr=0;ixr<rhs.c.size();ixr++)
995 Vnuc+=c[ixl].c*rhs.c[ixr].c*nuclear_int_os(xa,ya,za,c[ixl].z,cart,cx,cy,cz,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
996
997 // Transformation to spherical harmonics. Left side:
998 if(uselm) {
999 Vnuc=transmat*Vnuc;
1000 }
1001 // Right side
1002 if(rhs.uselm) {
1003 Vnuc=Vnuc*arma::trans(rhs.transmat);
1004 }
1005
1006 return Vnuc;
1007 }
1008
nuclear_pulay(double cx,double cy,double cz,const arma::mat & P,const GaussianShell & rhs) const1009 arma::vec GaussianShell::nuclear_pulay(double cx, double cy, double cz, const arma::mat & P, const GaussianShell & rhs) const {
1010 // Coordinates
1011 double xa=cen.x;
1012 double ya=cen.y;
1013 double za=cen.z;
1014
1015 double xb=rhs.cen.x;
1016 double yb=rhs.cen.y;
1017 double zb=rhs.cen.z;
1018
1019 // Derivative matrices
1020 std::vector<arma::mat> dermat(6);
1021 for(size_t i=0;i<dermat.size();i++)
1022 dermat[i].zeros(get_Ncart(),rhs.get_Ncart());
1023
1024 // Increment derivative matrices
1025 for(size_t ixl=0;ixl<c.size();ixl++)
1026 for(size_t ixr=0;ixr<rhs.c.size();ixr++) {
1027 std::vector<arma::mat> hlp=nuclear_int_pulay_os(xa,ya,za,c[ixl].z,cart,cx,cy,cz,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
1028 for(size_t i=0;i<dermat.size();i++)
1029 dermat[i]+=c[ixl].c*rhs.c[ixr].c*hlp[i];
1030 }
1031
1032 // Transformation to spherical harmonics. Left side:
1033 if(uselm)
1034 for(size_t i=0;i<dermat.size();i++)
1035 dermat[i]=transmat*dermat[i];
1036 // Right side
1037 if(rhs.uselm)
1038 for(size_t i=0;i<dermat.size();i++)
1039 dermat[i]=dermat[i]*arma::trans(rhs.transmat);
1040
1041 // Compute the force
1042 arma::vec ret(dermat.size());
1043 ret.zeros();
1044 for(size_t i=0;i<dermat.size();i++) {
1045 ret(i)=arma::trace(arma::trans(P)*dermat[i]);
1046 }
1047
1048 return ret;
1049 }
1050
nuclear_der(double cx,double cy,double cz,const arma::mat & P,const GaussianShell & rhs) const1051 arma::vec GaussianShell::nuclear_der(double cx, double cy, double cz, const arma::mat & P, const GaussianShell & rhs) const {
1052 // Coordinates
1053 double xa=cen.x;
1054 double ya=cen.y;
1055 double za=cen.z;
1056
1057 double xb=rhs.cen.x;
1058 double yb=rhs.cen.y;
1059 double zb=rhs.cen.z;
1060
1061 // Derivative matrices
1062 std::vector<arma::mat> dermat(3);
1063 for(size_t i=0;i<dermat.size();i++)
1064 dermat[i].zeros(get_Ncart(),rhs.get_Ncart());
1065
1066 // Increment derivative matrices
1067 for(size_t ixl=0;ixl<c.size();ixl++)
1068 for(size_t ixr=0;ixr<rhs.c.size();ixr++) {
1069 std::vector<arma::mat> hlp=nuclear_int_ders_os(xa,ya,za,c[ixl].z,cart,cx,cy,cz,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
1070 for(size_t i=0;i<dermat.size();i++) {
1071 dermat[i]+=c[ixl].c*rhs.c[ixr].c*hlp[i];
1072 }
1073 }
1074
1075 // Transformation to spherical harmonics. Left side:
1076 if(uselm)
1077 for(size_t i=0;i<dermat.size();i++)
1078 dermat[i]=transmat*dermat[i];
1079
1080 // Right side
1081 if(rhs.uselm)
1082 for(size_t i=0;i<dermat.size();i++)
1083 dermat[i]=dermat[i]*arma::trans(rhs.transmat);
1084
1085 // Compute the force
1086 arma::vec ret(dermat.size());
1087 ret.zeros();
1088 for(size_t i=0;i<dermat.size();i++) {
1089 ret(i)=arma::trace(arma::trans(P)*dermat[i]);
1090 }
1091
1092 return ret;
1093 }
1094
kinetic_pulay(const arma::mat & P,const GaussianShell & rhs) const1095 arma::vec GaussianShell::kinetic_pulay(const arma::mat & P, const GaussianShell & rhs) const {
1096 // Coordinates
1097 double xa=cen.x;
1098 double ya=cen.y;
1099 double za=cen.z;
1100
1101 double xb=rhs.cen.x;
1102 double yb=rhs.cen.y;
1103 double zb=rhs.cen.z;
1104
1105 std::vector<arma::mat> dermat(6);
1106 for(size_t i=0;i<dermat.size();i++)
1107 dermat[i].zeros(get_Ncart(),rhs.get_Ncart());
1108
1109 for(size_t ixl=0;ixl<c.size();ixl++)
1110 for(size_t ixr=0;ixr<rhs.c.size();ixr++) {
1111 std::vector<arma::mat> hlp=kinetic_int_pulay_os(xa,ya,za,c[ixl].z,cart,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
1112 for(size_t i=0;i<dermat.size();i++)
1113 dermat[i]+=c[ixl].c*rhs.c[ixr].c*hlp[i];
1114 }
1115
1116 // Transformation to spherical harmonics. Left side:
1117 if(uselm)
1118 for(size_t i=0;i<dermat.size();i++)
1119 dermat[i]=transmat*dermat[i];
1120 // Right side
1121 if(rhs.uselm)
1122 for(size_t i=0;i<dermat.size();i++)
1123 dermat[i]=dermat[i]*arma::trans(rhs.transmat);
1124
1125 // Compute the force
1126 arma::vec ret(dermat.size());
1127 ret.zeros();
1128 for(size_t i=0;i<dermat.size();i++) {
1129 ret(i)=arma::trace(arma::trans(P)*dermat[i]);
1130 }
1131
1132 return ret;
1133 }
1134
overlap_der(const arma::mat & W,const GaussianShell & rhs) const1135 arma::vec GaussianShell::overlap_der(const arma::mat & W, const GaussianShell & rhs) const {
1136 // Coordinates
1137 double xa=cen.x;
1138 double ya=cen.y;
1139 double za=cen.z;
1140
1141 double xb=rhs.cen.x;
1142 double yb=rhs.cen.y;
1143 double zb=rhs.cen.z;
1144
1145 std::vector<arma::mat> dermat(6);
1146 for(size_t i=0;i<dermat.size();i++)
1147 dermat[i].zeros(get_Ncart(),rhs.get_Ncart());
1148
1149 for(size_t ixl=0;ixl<c.size();ixl++)
1150 for(size_t ixr=0;ixr<rhs.c.size();ixr++) {
1151 std::vector<arma::mat> hlp=overlap_int_pulay_os(xa,ya,za,c[ixl].z,cart,xb,yb,zb,rhs.c[ixr].z,rhs.cart);
1152 for(size_t i=0;i<dermat.size();i++)
1153 dermat[i]+=c[ixl].c*rhs.c[ixr].c*hlp[i];
1154 }
1155
1156 // Transformation to spherical harmonics. Left side:
1157 if(uselm)
1158 for(size_t i=0;i<dermat.size();i++)
1159 dermat[i]=transmat*dermat[i];
1160 // Right side
1161 if(rhs.uselm)
1162 for(size_t i=0;i<dermat.size();i++)
1163 dermat[i]=dermat[i]*arma::trans(rhs.transmat);
1164
1165 // Compute the force
1166 arma::vec ret(dermat.size());
1167 ret.zeros();
1168 for(size_t i=0;i<dermat.size();i++) {
1169 ret(i)=-arma::trace(arma::trans(W)*dermat[i]);
1170 }
1171
1172 return ret;
1173 }
1174
moment(int momam,double x,double y,double z,const GaussianShell & rhs) const1175 std::vector<arma::mat> GaussianShell::moment(int momam, double x, double y, double z, const GaussianShell & rhs) const {
1176 // Calculate moment integrals around (x,y,z) between shells
1177
1178 // Amount of moments is
1179 size_t Nmom=(momam+1)*(momam+2)/2;
1180
1181 // Moments to compute:
1182 std::vector<shellf_t> mom;
1183 mom.reserve(Nmom);
1184 for(int ii=0; ii<=momam; ii++) {
1185 int lc=momam - ii;
1186 for(int jj=0; jj<=ii; jj++) {
1187 int mc=ii - jj;
1188 int nc=jj;
1189
1190 shellf_t tmp;
1191 tmp.l=lc;
1192 tmp.m=mc;
1193 tmp.n=nc;
1194 tmp.relnorm=1.0;
1195 mom.push_back(tmp);
1196 }
1197 }
1198
1199 // Temporary array, place moment last so we can use slice()
1200 arma::cube wrk(cart.size(),rhs.cart.size(),Nmom);
1201 wrk.zeros();
1202
1203 // Coordinates
1204 double xa=cen.x;
1205 double ya=cen.y;
1206 double za=cen.z;
1207 double xb=rhs.cen.x;
1208 double yb=rhs.cen.y;
1209 double zb=rhs.cen.z;
1210
1211 // Compute moment integrals
1212 for(size_t ixl=0;ixl<c.size();ixl++) {
1213 double ca=c[ixl].c;
1214 double zetaa=c[ixl].z;
1215
1216 for(size_t ixr=0;ixr<rhs.c.size();ixr++) {
1217 double cb=rhs.c[ixr].c;
1218 double zetab=rhs.c[ixr].z;
1219
1220 wrk+=ca*cb*three_overlap_int_os(xa,ya,za,xb,yb,zb,x,y,z,zetaa,zetab,0.0,cart,rhs.cart,mom);
1221 }
1222 }
1223
1224 // Collect the results
1225 std::vector<arma::mat> ret;
1226 ret.reserve(Nmom);
1227 for(size_t m=0;m<Nmom;m++) {
1228 // The matrix for this moment is
1229 arma::mat momval=wrk.slice(m);
1230
1231 // Convert to spherical basis if necessary
1232 if(uselm) {
1233 momval=transmat*momval;
1234 }
1235 // Right side
1236 if(rhs.uselm) {
1237 momval=momval*arma::trans(rhs.transmat);
1238 }
1239
1240 // Add it to the stack
1241 ret.push_back(momval);
1242 }
1243
1244 return ret;
1245 }
1246
integral() const1247 arma::vec GaussianShell::integral() const {
1248 // Compute integrals over the cartesian functions
1249 arma::vec ints(cart.size());
1250 ints.zeros();
1251
1252 // Loop over cartesians
1253 for(size_t ic=0;ic<cart.size();ic++) {
1254 int l=cart[ic].l;
1255 int m=cart[ic].m;
1256 int n=cart[ic].n;
1257
1258 if(l%2 || m%2 || n%2)
1259 // Odd function - zero integral
1260 continue;
1261
1262 // Loop over exponents
1263 for(size_t ix=0;ix<c.size();ix++) {
1264 double zeta=c[ix].z;
1265
1266 // Integral over x gives
1267 double intx=2.0*pow(0.5/sqrt(zeta),l+1)*sqrt(M_PI);
1268 // Integral over y
1269 double inty=2.0*pow(0.5/sqrt(zeta),m+1)*sqrt(M_PI);
1270 // Integral over z
1271 double intz=2.0*pow(0.5/sqrt(zeta),n+1)*sqrt(M_PI);
1272
1273 // Increment total integral
1274 ints(ic)+=c[ix].c*intx*inty*intz;
1275 }
1276
1277 // Plug in relative norm
1278 ints(ic)*=cart[ic].relnorm;
1279 }
1280
1281 // Do conversion to spherical basis
1282 if(uselm)
1283 ints=transmat*ints;
1284
1285 return ints;
1286 }
1287
1288
BasisSet()1289 BasisSet::BasisSet() {
1290 // Use spherical harmonics and cartesian functions by default.
1291 uselm=true;
1292 optlm=true;
1293 }
1294
1295 extern Settings settings;
1296
BasisSet(size_t Nat)1297 BasisSet::BasisSet(size_t Nat) {
1298 // Use spherical harmonics?
1299 uselm=settings.get_bool("UseLM");
1300 optlm=settings.get_bool("OptLM");
1301
1302 shells.reserve(Nat);
1303 nuclei.reserve(Nat);
1304 }
1305
~BasisSet()1306 BasisSet::~BasisSet() {
1307 }
1308
add_nucleus(const nucleus_t & nuc)1309 void BasisSet::add_nucleus(const nucleus_t & nuc) {
1310 nuclei.push_back(nuc);
1311 // Clear list of functions
1312 nuclei[nuclei.size()-1].shells.clear();
1313 // Set nuclear index
1314 nuclei[nuclei.size()-1].ind=nuclei.size()-1;
1315 }
1316
add_shell(size_t nucind,const GaussianShell & sh,bool dosort)1317 void BasisSet::add_shell(size_t nucind, const GaussianShell & sh, bool dosort) {
1318 if(nucind>=nuclei.size()) {
1319 ERROR_INFO();
1320 throw std::runtime_error("Cannot add functions to nonexisting nucleus!\n");
1321 }
1322
1323 // Add shell
1324 shells.push_back(sh);
1325 // Set pointer to nucleus
1326 shells[shells.size()-1].set_center(nuclei[nucind].r,nucind);
1327
1328 // Sort the basis set, updating the nuclear list and basis function indices as well
1329 if(dosort)
1330 sort();
1331 else {
1332 // Just do the numbering and shell list updates.
1333 check_numbering();
1334 update_nuclear_shell_list();
1335 }
1336 }
1337
add_shell(size_t nucind,int am,bool lm,const std::vector<contr_t> & C,bool dosort)1338 void BasisSet::add_shell(size_t nucind, int am, bool lm, const std::vector<contr_t> & C, bool dosort) {
1339 // Create new shell.
1340 GaussianShell sh=GaussianShell(am,lm,C);
1341 // Do the rest here
1342 add_shell(nucind,sh,dosort);
1343 }
1344
add_shells(size_t nucind,ElementBasisSet el,bool dosort)1345 void BasisSet::add_shells(size_t nucind, ElementBasisSet el, bool dosort) {
1346 // Add basis functions at cen
1347
1348 // Get the shells on the element
1349 std::vector<FunctionShell> bf=el.get_shells();
1350
1351 // Loop over shells in element basis
1352 for(size_t i=0;i<bf.size();i++) {
1353 // Create shell
1354 GaussianShell sh;
1355 if(!optlm || bf[i].get_am()>=2)
1356 sh=GaussianShell(bf[i].get_am(),uselm,bf[i].get_contr());
1357 else
1358 sh=GaussianShell(bf[i].get_am(),false,bf[i].get_contr());
1359
1360 // and add it
1361 add_shell(nucind,sh,dosort);
1362 }
1363 }
1364
check_numbering()1365 void BasisSet::check_numbering() {
1366 // Renumber basis functions
1367 size_t ind=0;
1368 for(size_t i=0;i<shells.size();i++) {
1369 shells[i].set_first_ind(ind);
1370 ind=shells[i].get_last_ind()+1;
1371 }
1372 }
1373
update_nuclear_shell_list()1374 void BasisSet::update_nuclear_shell_list() {
1375 // First, clear the list on all nuclei.
1376 for(size_t inuc=0;inuc<nuclei.size();inuc++)
1377 nuclei[inuc].shells.clear();
1378
1379 // Then, update the lists. Loop over shells
1380 for(size_t ish=0;ish<shells.size();ish++) {
1381 // Find out nuclear index
1382 size_t inuc=shells[ish].get_center_ind();
1383 // Add pointer to the nucleus
1384 nuclei[inuc].shells.push_back(&shells[ish]);
1385 }
1386 }
1387
sort()1388 void BasisSet::sort() {
1389 // Sort the shells first by increasing index of center, then by
1390 // increasing angular momentum and last by decreasing exponent.
1391 std::stable_sort(shells.begin(),shells.end());
1392
1393 // Check the numbering of the basis functions
1394 check_numbering();
1395
1396 // and since we probably have changed the order of the basis
1397 // functions, we need to update the list of functions on the nuclei.
1398 update_nuclear_shell_list();
1399 }
1400
compute_nuclear_distances()1401 void BasisSet::compute_nuclear_distances() {
1402 // Amount of nuclei
1403 size_t N=nuclei.size();
1404
1405 // Reserve memory
1406 nucleardist=arma::mat(N,N);
1407
1408 double d;
1409
1410 // Fill table
1411 for(size_t i=0;i<N;i++)
1412 for(size_t j=0;j<=i;j++) {
1413 d=dist(nuclei[i].r.x,nuclei[i].r.y,nuclei[i].r.z,nuclei[j].r.x,nuclei[j].r.y,nuclei[j].r.z);
1414
1415 nucleardist(i,j)=d;
1416 nucleardist(j,i)=d;
1417 }
1418 }
1419
nuclear_distance(size_t i,size_t j) const1420 double BasisSet::nuclear_distance(size_t i, size_t j) const {
1421 return nucleardist(i,j);
1422 }
1423
nuclear_distances() const1424 arma::mat BasisSet::nuclear_distances() const {
1425 return nucleardist;
1426 }
1427
operator <(const shellpair_t & lhs,const shellpair_t & rhs)1428 bool operator<(const shellpair_t & lhs, const shellpair_t & rhs) {
1429 // Helper for ordering shellpairs into libint order
1430 return (lhs.li+lhs.lj)<(rhs.li+rhs.lj);
1431 }
1432
form_unique_shellpairs()1433 void BasisSet::form_unique_shellpairs() {
1434 // Drop list of existing pairs.
1435 shellpairs.clear();
1436
1437 // Form list of unique shell pairs.
1438 shellpair_t tmp;
1439
1440 // Now, form list of unique shell pairs
1441 for(size_t i=0;i<shells.size();i++) {
1442 for(size_t j=0;j<=i;j++) {
1443 // Have to set these in every iteration due to swap below
1444 tmp.is=i;
1445 tmp.js=j;
1446
1447 // Check that libint's angular momentum rules are satisfied
1448 if(shells[j].get_am()>shells[i].get_am())
1449 std::swap(tmp.is,tmp.js);
1450
1451 // Set angular momenta
1452 tmp.li=shells[tmp.is].get_am();
1453 tmp.lj=shells[tmp.js].get_am();
1454
1455 shellpairs.push_back(tmp);
1456 }
1457 }
1458
1459 // Sort list of unique shell pairs
1460 stable_sort(shellpairs.begin(),shellpairs.end());
1461
1462 /*
1463 // Print list
1464 printf("\nList of unique shell pairs (%lu pairs):\n",shellpairs.size());
1465 for(size_t ind=0;ind<shellpairs.size();ind++) {
1466 size_t i=shellpairs[ind].is;
1467 size_t j=shellpairs[ind].js;
1468
1469 int li=shells[i].get_am();
1470 int lj=shells[j].get_am();
1471
1472 printf("%i\t%i\t%i\t%i\t%i\n",(int) i,(int) j,li,lj,li+lj);
1473 }
1474 */
1475 }
1476
get_unique_shellpairs() const1477 std::vector<shellpair_t> BasisSet::get_unique_shellpairs() const {
1478 if(shells.size() && !shellpairs.size()) {
1479 throw std::runtime_error("shellpairs not initialized! Maybe you forgot to finalize?\n");
1480 }
1481
1482 return shellpairs;
1483 }
1484
get_eripairs(arma::mat & Q,arma::mat & M,double tol,double omega,double alpha,double beta,bool verbose) const1485 std::vector<eripair_t> BasisSet::get_eripairs(arma::mat & Q, arma::mat & M, double tol, double omega, double alpha, double beta, bool verbose) const {
1486 // Get the screening matrices
1487 eri_screening(Q,M,omega,alpha,beta);
1488
1489 // Fill out list
1490 std::vector<eripair_t> list(shellpairs.size());
1491 for(size_t i=0;i<shellpairs.size();i++) {
1492 list[i].is=shellpairs[i].is;
1493 list[i].i0=shells[shellpairs[i].is].get_first_ind();
1494 list[i].Ni=shells[shellpairs[i].is].get_Nbf();
1495
1496 list[i].js=shellpairs[i].js;
1497 list[i].j0=shells[shellpairs[i].js].get_first_ind();
1498 list[i].Nj=shells[shellpairs[i].js].get_Nbf();
1499
1500 list[i].eri=Q(list[i].is,list[i].js);
1501 }
1502 // and sort it
1503 std::stable_sort(list.begin(),list.end());
1504
1505 // Find out which pairs are negligible.
1506 size_t ulimit=list.size()-1;
1507 // An integral (ij|kl) <= sqrt( (ij|ij) (kl|kl) ). Since the
1508 // integrals are in decreasing order, the integral threshold is
1509 double thr=tol/list[0].eri;
1510 while(list[ulimit].eri < thr)
1511 ulimit--;
1512 if(ulimit<list.size())
1513 list.resize(ulimit+1);
1514
1515 (void) verbose;
1516 //if(verbose)
1517 // printf("%u shell pairs out of %u are significant.\n",(unsigned int) list.size(),(unsigned int) shellpairs.size());
1518
1519 /*
1520 FILE *out=fopen("screen.dat","w");
1521 for(size_t i=0;i<list.size();i++)
1522 fprintf(out,"%4i %4i %e\n",(int) list[i].is, (int) list[i].js, list[i].eri);
1523 fclose(out);
1524 */
1525
1526 return list;
1527 }
1528
operator <(const eripair_t & lhs,const eripair_t & rhs)1529 bool operator<(const eripair_t & lhs, const eripair_t & rhs) {
1530 // Sort in decreasing order!
1531 return lhs.eri > rhs.eri;
1532 }
1533
1534
finalize(bool convert,bool donorm)1535 void BasisSet::finalize(bool convert, bool donorm) {
1536 // Finalize basis set structure for use.
1537
1538 // Compute nuclear distances.
1539 compute_nuclear_distances();
1540
1541 // Compute ranges of shells
1542 compute_shell_ranges();
1543
1544 // Convert contractions
1545 if(convert)
1546 convert_contractions();
1547 // Normalize contractions if requested, and compute cartesian norms
1548 normalize(donorm);
1549
1550 // Form list of unique shell pairs
1551 form_unique_shellpairs();
1552 // and update the nuclear shell list (in case the basis set was
1553 // loaded from checkpoint)
1554 update_nuclear_shell_list();
1555 }
1556
get_am(size_t ind) const1557 int BasisSet::get_am(size_t ind) const {
1558 return shells[ind].get_am();
1559 }
1560
get_max_am() const1561 int BasisSet::get_max_am() const {
1562 if(shells.size()==0) {
1563 ERROR_INFO();
1564 throw std::domain_error("Cannot get maximum angular momentum of an empty basis set!\n");
1565 }
1566
1567 int maxam=shells[0].get_am();
1568 for(size_t i=1;i<shells.size();i++)
1569 if(shells[i].get_am()>maxam)
1570 maxam=shells[i].get_am();
1571 return maxam;
1572 }
1573
get_max_Ncontr() const1574 size_t BasisSet::get_max_Ncontr() const {
1575 size_t maxc=shells[0].get_Ncontr();
1576 for(size_t i=1;i<shells.size();i++)
1577 if(shells[i].get_Ncontr()>maxc)
1578 maxc=shells[i].get_Ncontr();
1579 return maxc;
1580 }
1581
get_Nbf() const1582 size_t BasisSet::get_Nbf() const {
1583 if(shells.size())
1584 return shells[shells.size()-1].get_last_ind()+1;
1585 else
1586 return 0;
1587 }
1588
compute_shell_ranges(double eps)1589 void BasisSet::compute_shell_ranges(double eps) {
1590 shell_ranges=get_shell_ranges(eps);
1591 }
1592
get_shell_ranges() const1593 std::vector<double> BasisSet::get_shell_ranges() const {
1594 return shell_ranges;
1595 }
1596
get_shell_ranges(double eps) const1597 std::vector<double> BasisSet::get_shell_ranges(double eps) const {
1598 std::vector<double> shran(shells.size());
1599 #ifdef _OPENMP
1600 #pragma omp parallel for
1601 #endif
1602 for(size_t i=0;i<shells.size();i++)
1603 shran[i]=shells[i].range(eps);
1604
1605 return shran;
1606 }
1607
get_nuclear_distances(size_t inuc) const1608 std::vector<double> BasisSet::get_nuclear_distances(size_t inuc) const {
1609 std::vector<double> d(nucleardist.n_cols);
1610 for(size_t i=0;i<nucleardist.n_cols;i++)
1611 d[i]=nucleardist(inuc,i);
1612 return d;
1613 }
1614
get_Ncart() const1615 size_t BasisSet::get_Ncart() const {
1616 size_t n=0;
1617 for(size_t i=0;i<shells.size();i++)
1618 n+=shells[i].get_Ncart();
1619 return n;
1620 }
1621
get_Nlm() const1622 size_t BasisSet::get_Nlm() const {
1623 size_t n=0;
1624 for(size_t i=0;i<shells.size();i++)
1625 n+=shells[i].get_Nlm();
1626 return n;
1627 }
1628
get_Nbf(size_t ind) const1629 size_t BasisSet::get_Nbf(size_t ind) const {
1630 return shells[ind].get_Nbf();
1631 }
1632
get_Ncart(size_t ind) const1633 size_t BasisSet::get_Ncart(size_t ind) const {
1634 return shells[ind].get_Ncart();
1635 }
1636
get_last_ind() const1637 size_t BasisSet::get_last_ind() const {
1638 if(shells.size())
1639 return shells[shells.size()-1].get_last_ind();
1640 else {
1641 std::ostringstream oss;
1642 oss << "\nError in function " << __FUNCTION__ << "(file " << __FILE__ << ", near line " << __LINE__ << "\nCannot get number of last basis function of an empty basis set!\n";
1643 throw std::domain_error(oss.str());
1644 }
1645 }
1646
get_first_ind(size_t num) const1647 size_t BasisSet::get_first_ind(size_t num) const {
1648 return shells[num].get_first_ind();
1649 }
1650
get_last_ind(size_t num) const1651 size_t BasisSet::get_last_ind(size_t num) const {
1652 return shells[num].get_last_ind();
1653 }
1654
get_bf_Rsquared() const1655 arma::vec BasisSet::get_bf_Rsquared() const {
1656 arma::vec Rsq(get_Nbf());
1657 for(size_t i=0;i<shells.size();i++) {
1658 // First function on shell
1659 size_t i0=shells[i].get_first_ind();
1660 // Number of functions
1661 size_t nbf=shells[i].get_Nbf();
1662
1663 // Get coordinates of shell center
1664 coords_t cen=shells[i].get_center();
1665 // Calculate moment integrals
1666 std::vector<arma::mat> mom2=shells[i].moment(2, cen.x, cen.y, cen.z, shells[i]);
1667 // Compute spatial extents
1668 for(size_t fi=0;fi<nbf;fi++)
1669 Rsq(i0+fi)=mom2[getind(2,0,0)](fi,fi)+mom2[getind(0,2,0)](fi,fi)+mom2[getind(0,0,2)](fi,fi);
1670 }
1671
1672 return Rsq;
1673 }
1674
shell_indices() const1675 arma::uvec BasisSet::shell_indices() const {
1676 arma::uvec idx(get_Nbf());
1677 for(size_t i=0;i<shells.size();i++)
1678 idx.subvec(shells[i].get_first_ind(),shells[i].get_last_ind())=i*arma::ones<arma::uvec>(shells[i].get_Nbf());
1679 return idx;
1680 }
1681
find_shell_ind(size_t find) const1682 size_t BasisSet::find_shell_ind(size_t find) const {
1683 // Find shell the function belongs to
1684 for(size_t i=0;i<shells.size();i++)
1685 if(find>=shells[i].get_first_ind() && find<=shells[i].get_last_ind())
1686 return i;
1687
1688 std::ostringstream oss;
1689 oss << "Basis function " << find << " not found in basis set!\n";
1690 throw std::runtime_error(oss.str());
1691 }
1692
get_shell_center_ind(size_t num) const1693 size_t BasisSet::get_shell_center_ind(size_t num) const {
1694 return shells[num].get_center_ind();
1695 }
1696
get_shells() const1697 std::vector<GaussianShell> BasisSet::get_shells() const {
1698 return shells;
1699 }
1700
get_shell(size_t ind) const1701 GaussianShell BasisSet::get_shell(size_t ind) const {
1702 return shells[ind];
1703 }
1704
get_shell_center(size_t num) const1705 coords_t BasisSet::get_shell_center(size_t num) const {
1706 return shells[num].get_center();
1707 }
1708
get_contr(size_t ind) const1709 std::vector<contr_t> BasisSet::get_contr(size_t ind) const {
1710 return shells[ind].get_contr();
1711 }
1712
get_contr_normalized(size_t ind) const1713 std::vector<contr_t> BasisSet::get_contr_normalized(size_t ind) const {
1714 return shells[ind].get_contr_normalized();
1715 }
1716
get_cart(size_t ind) const1717 std::vector<shellf_t> BasisSet::get_cart(size_t ind) const {
1718 return shells[ind].get_cart();
1719 }
1720
1721
is_lm_default() const1722 bool BasisSet::is_lm_default() const {
1723 return uselm;
1724 }
1725
lm_in_use(size_t num) const1726 bool BasisSet::lm_in_use(size_t num) const {
1727 return shells[num].lm_in_use();
1728 }
1729
set_lm(size_t num,bool lm)1730 void BasisSet::set_lm(size_t num, bool lm) {
1731 // Set use of spherical harmonics
1732 shells[num].set_lm(lm);
1733 // Check numbering of basis functions which may have changed
1734 check_numbering();
1735 }
1736
get_m_values() const1737 arma::ivec BasisSet::get_m_values() const {
1738 arma::ivec ret(get_Nbf());
1739 for(size_t is=0;is<get_Nshells();is++) {
1740 // Angular momentum is
1741 int am(get_am(is));
1742
1743 // First function on shell
1744 size_t i0(get_first_ind(is));
1745
1746 // Functions are -m, -m+1, ..., m-1, m
1747 if(lm_in_use(is)) {
1748 ret.subvec(i0,i0+2*am)=arma::linspace<arma::ivec>(-am,am,2*am+1);
1749 } else {
1750 if(am==0)
1751 ret(i0)=0;
1752 else if(am==1) {
1753 // Functions are in order x, y, z i.e. 1, -1, 0
1754 ret(i0)=1;
1755 ret(i0+1)=-1;
1756 ret(i0+2)=0;
1757 } else
1758 throw std::logic_error("Need to use spherical basis for linear symmetry!\n");
1759 }
1760 }
1761
1762 return ret;
1763 }
1764
unique_m_values() const1765 arma::ivec BasisSet::unique_m_values() const {
1766 // Find unique m values
1767 arma::ivec mval(get_m_values());
1768 arma::uvec muni_idx(arma::find_unique(mval));
1769 arma::ivec muni(mval(muni_idx));
1770 return arma::sort(muni);
1771 }
1772
unique_m_map() const1773 std::map<int, arma::uword> BasisSet::unique_m_map() const {
1774 arma::ivec muni(unique_m_values());
1775 std::map<int, arma::uword> mlook;
1776 for(arma::uword i=0;i<muni.size();i++)
1777 mlook[muni(i)]=i;
1778 return mlook;
1779 }
1780
count_m_occupied(const arma::mat & C) const1781 arma::imat BasisSet::count_m_occupied(const arma::mat & C) const {
1782 arma::ivec mc(m_classify(C,get_m_values()));
1783
1784 std::map<int, arma::uword> mlook(unique_m_map());
1785
1786 arma::imat occ;
1787 occ.zeros(mlook.size(),3);
1788 for(size_t i=0;i<C.n_cols;i++)
1789 occ(mlook[mc(i)],0)++;
1790 occ.col(1)=occ.col(0);
1791 occ.col(2)=unique_m_values();
1792 return occ;
1793 }
1794
count_m_occupied(const arma::mat & Ca,const arma::mat & Cb) const1795 arma::imat BasisSet::count_m_occupied(const arma::mat & Ca, const arma::mat & Cb) const {
1796 arma::ivec mca(m_classify(Ca,get_m_values()));
1797 arma::ivec mcb(m_classify(Cb,get_m_values()));
1798
1799 std::map<int, arma::uword> mlook(unique_m_map());
1800
1801 arma::imat occ;
1802 occ.zeros(mlook.size(),3);
1803 for(size_t i=0;i<Ca.n_cols;i++)
1804 occ(mlook[mca(i)],0)++;
1805 for(size_t i=0;i<Cb.n_cols;i++)
1806 occ(mlook[mcb(i)],1)++;
1807 occ.col(2)=unique_m_values();
1808 return occ;
1809 }
1810
m_indices(int mwant) const1811 arma::uvec BasisSet::m_indices(int mwant) const {
1812 arma::ivec midx(get_m_values());
1813 return arma::find(midx==mwant);
1814 }
1815
get_trans(size_t ind) const1816 arma::mat BasisSet::get_trans(size_t ind) const {
1817 return shells[ind].get_trans();
1818 }
1819
get_Nshells() const1820 size_t BasisSet::get_Nshells() const {
1821 return shells.size();
1822 }
1823
get_Nnuc() const1824 size_t BasisSet::get_Nnuc() const {
1825 return nuclei.size();
1826 }
1827
get_nucleus(size_t inuc) const1828 nucleus_t BasisSet::get_nucleus(size_t inuc) const {
1829 return nuclei[inuc];
1830 }
1831
get_nuclei() const1832 std::vector<nucleus_t> BasisSet::get_nuclei() const {
1833 return nuclei;
1834 }
1835
get_nuclear_coords() const1836 arma::mat BasisSet::get_nuclear_coords() const {
1837 arma::mat coords(nuclei.size(),3);
1838 for(size_t i=0;i<nuclei.size();i++)
1839 coords.row(i)=arma::trans(coords_to_vec(nuclei[i].r));
1840
1841 return coords;
1842 }
1843
set_nuclear_coords(const arma::mat & c)1844 void BasisSet::set_nuclear_coords(const arma::mat & c) {
1845 if(c.n_rows != nuclei.size() || c.n_cols != 3)
1846 throw std::logic_error("Coordinates matrix does not match nuclei!\n");
1847
1848 for(size_t i=0;i<nuclei.size();i++)
1849 nuclei[i].r=vec_to_coords(arma::trans(c.row(i)));
1850
1851 // Update shell centers
1852 for(size_t i=0;i<shells.size();i++) {
1853 size_t icen=shells[i].get_center_ind();
1854 shells[i].set_center(nuclei[icen].r,icen);
1855 }
1856 // Update listings
1857 finalize(false,false);
1858 }
1859
get_nuclear_coords(size_t inuc) const1860 coords_t BasisSet::get_nuclear_coords(size_t inuc) const {
1861 return nuclei[inuc].r;
1862 }
1863
get_Z(size_t inuc) const1864 int BasisSet::get_Z(size_t inuc) const {
1865 return nuclei[inuc].Z;
1866 }
1867
get_symbol(size_t inuc) const1868 std::string BasisSet::get_symbol(size_t inuc) const {
1869 return nuclei[inuc].symbol;
1870 }
1871
get_symbol_hr(size_t inuc) const1872 std::string BasisSet::get_symbol_hr(size_t inuc) const {
1873 if(nuclei[inuc].bsse)
1874 return nuclei[inuc].symbol+"-Bq";
1875 else
1876 return nuclei[inuc].symbol;
1877 }
1878
get_funcs(size_t inuc) const1879 std::vector<GaussianShell> BasisSet::get_funcs(size_t inuc) const {
1880 std::vector<GaussianShell> ret;
1881 for(size_t i=0;i<nuclei[inuc].shells.size();i++)
1882 ret.push_back(*(nuclei[inuc].shells[i]));
1883
1884 return ret;
1885 }
1886
get_shell_inds(size_t inuc) const1887 std::vector<size_t> BasisSet::get_shell_inds(size_t inuc) const {
1888 std::vector<size_t> ret;
1889 for(size_t i=0;i<shells.size();i++)
1890 if(shells[i].get_center_ind()==inuc)
1891 ret.push_back(i);
1892 return ret;
1893 }
1894
eval_func(double x,double y,double z) const1895 arma::vec BasisSet::eval_func(double x, double y, double z) const {
1896 // Helper
1897 coords_t r;
1898 r.x=x;
1899 r.y=y;
1900 r.z=z;
1901
1902 // Determine which shells might contribute
1903 std::vector<size_t> compute_shells;
1904 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
1905 // Determine distance to nucleus
1906 double dist=norm(r-nuclei[inuc].r);
1907 // Get indices of shells centered on nucleus
1908 std::vector<size_t> shellinds=get_shell_inds(inuc);
1909
1910 // Loop over shells on nucleus
1911 for(size_t ish=0;ish<shellinds.size();ish++)
1912 // Shell is relevant if range is larger than distance
1913 if(dist < shell_ranges[shellinds[ish]])
1914 compute_shells.push_back(shellinds[ish]);
1915 }
1916
1917 // Returned values
1918 arma::vec ret(get_Nbf());
1919 ret.zeros();
1920 #ifdef _OPENMP
1921 #pragma omp parallel for
1922 #endif
1923 for(size_t i=0;i<compute_shells.size();i++) {
1924 size_t ish=compute_shells[i];
1925
1926 // Evalute shell. Function values
1927 arma::vec shf=shells[ish].eval_func(x,y,z);
1928 // First function on shell
1929 size_t f0=shells[ish].get_first_ind();
1930
1931 // and store the functions
1932 for(size_t fi=0;fi<shells[ish].get_Nbf();fi++) {
1933 ret(f0+fi)=shf(fi);
1934 }
1935 }
1936
1937 return ret;
1938 }
1939
eval_grad(double x,double y,double z) const1940 arma::mat BasisSet::eval_grad(double x, double y, double z) const {
1941 // Helper
1942 coords_t r;
1943 r.x=x;
1944 r.y=y;
1945 r.z=z;
1946
1947 // Determine which shells might contribute
1948 std::vector<size_t> compute_shells;
1949 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
1950 // Determine distance to nucleus
1951 double dist=norm(r-nuclei[inuc].r);
1952 // Get indices of shells centered on nucleus
1953 std::vector<size_t> shellinds=get_shell_inds(inuc);
1954
1955 // Loop over shells on nucleus
1956 for(size_t ish=0;ish<shellinds.size();ish++)
1957 // Shell is relevant if range is larger than distance
1958 if(dist < shell_ranges[shellinds[ish]])
1959 compute_shells.push_back(shellinds[ish]);
1960 }
1961
1962 // Returned values
1963 arma::mat ret(get_Nbf(),3);
1964 ret.zeros();
1965 #ifdef _OPENMP
1966 #pragma omp parallel for
1967 #endif
1968 for(size_t i=0;i<compute_shells.size();i++) {
1969 size_t ish=compute_shells[i];
1970
1971 // Evalute shell. Gradient values
1972 arma::mat gf=shells[ish].eval_grad(x,y,z);
1973 // First function on shell
1974 size_t f0=shells[ish].get_first_ind();
1975
1976 // and store the functions
1977 for(size_t fi=0;fi<shells[ish].get_Nbf();fi++) {
1978 ret.row(f0+fi)=gf.row(fi);
1979 }
1980 }
1981
1982 return ret;
1983 }
1984
eval_hess(double x,double y,double z) const1985 arma::mat BasisSet::eval_hess(double x, double y, double z) const {
1986 // Helper
1987 coords_t r;
1988 r.x=x;
1989 r.y=y;
1990 r.z=z;
1991
1992 // Determine which shells might contribute
1993 std::vector<size_t> compute_shells;
1994 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
1995 // Determine distance to nucleus
1996 double dist=norm(r-nuclei[inuc].r);
1997 // Get indices of shells centered on nucleus
1998 std::vector<size_t> shellinds=get_shell_inds(inuc);
1999
2000 // Loop over shells on nucleus
2001 for(size_t ish=0;ish<shellinds.size();ish++)
2002 // Shell is relevant if range is larger than distance
2003 if(dist < shell_ranges[shellinds[ish]])
2004 compute_shells.push_back(shellinds[ish]);
2005 }
2006
2007 // Returned values
2008 arma::mat ret(get_Nbf(),9);
2009 ret.zeros();
2010 #ifdef _OPENMP
2011 #pragma omp parallel for
2012 #endif
2013 for(size_t i=0;i<compute_shells.size();i++) {
2014 size_t ish=compute_shells[i];
2015
2016 // Evalute shell. Gradient values
2017 arma::mat gf=shells[ish].eval_hess(x,y,z);
2018 // First function on shell
2019 size_t f0=shells[ish].get_first_ind();
2020
2021 // and store the functions
2022 for(size_t fi=0;fi<shells[ish].get_Nbf();fi++) {
2023 ret.row(f0+fi)=gf.row(fi);
2024 }
2025 }
2026
2027 return ret;
2028 }
2029
eval_func(size_t ish,double x,double y,double z) const2030 arma::vec BasisSet::eval_func(size_t ish, double x, double y, double z) const {
2031 return shells[ish].eval_func(x,y,z);
2032 }
2033
eval_grad(size_t ish,double x,double y,double z) const2034 arma::mat BasisSet::eval_grad(size_t ish, double x, double y, double z) const {
2035 return shells[ish].eval_grad(x,y,z);
2036 }
2037
eval_lapl(size_t ish,double x,double y,double z) const2038 arma::vec BasisSet::eval_lapl(size_t ish, double x, double y, double z) const {
2039 return shells[ish].eval_lapl(x,y,z);
2040 }
2041
eval_hess(size_t ish,double x,double y,double z) const2042 arma::mat BasisSet::eval_hess(size_t ish, double x, double y, double z) const {
2043 return shells[ish].eval_hess(x,y,z);
2044 }
2045
eval_laplgrad(size_t ish,double x,double y,double z) const2046 arma::mat BasisSet::eval_laplgrad(size_t ish, double x, double y, double z) const {
2047 return shells[ish].eval_laplgrad(x,y,z);
2048 }
2049
convert_contractions()2050 void BasisSet::convert_contractions() {
2051 for(size_t i=0;i<shells.size();i++)
2052 shells[i].convert_contraction();
2053 }
2054
convert_contraction(size_t ind)2055 void BasisSet::convert_contraction(size_t ind) {
2056 shells[ind].convert_contraction();
2057 }
2058
normalize(bool coeffs)2059 void BasisSet::normalize(bool coeffs) {
2060 for(size_t i=0;i<shells.size();i++)
2061 shells[i].normalize(coeffs);
2062 }
2063
coulomb_normalize()2064 void BasisSet::coulomb_normalize() {
2065 for(size_t i=0;i<shells.size();i++)
2066 shells[i].coulomb_normalize();
2067 }
2068
print(bool verbose) const2069 void BasisSet::print(bool verbose) const {
2070 printf("There are %i shells and %i nuclei in the basis set.\n\n",(int) shells.size(),(int) nuclei.size());
2071
2072 printf("List of nuclei, geometry in Ångström with three decimal places:\n");
2073
2074 printf("\t\t Z\t x\t y\t z\n");
2075 for(size_t i=0;i<nuclei.size();i++) {
2076 if(nuclei[i].bsse)
2077 printf("%i\t%s\t*%i\t% 7.3f\t% 7.3f\t% 7.3f\n",(int) i+1,nuclei[i].symbol.c_str(),nuclei[i].Z,nuclei[i].r.x/ANGSTROMINBOHR,nuclei[i].r.y/ANGSTROMINBOHR,nuclei[i].r.z/ANGSTROMINBOHR);
2078 else
2079 printf("%i\t%s\t %i\t% 7.3f\t% 7.3f\t% 7.3f\n",(int) i+1,nuclei[i].symbol.c_str(),nuclei[i].Z,nuclei[i].r.x/ANGSTROMINBOHR,nuclei[i].r.y/ANGSTROMINBOHR,nuclei[i].r.z/ANGSTROMINBOHR);
2080 }
2081
2082 if(nuclei.size()>1 && nuclei.size()<=13) {
2083 // Legend length is 7 + 6*(N-1) chars
2084
2085 // Print legend
2086 printf("\nInteratomic distance matrix:\n%7s","");
2087 for(size_t i=0;i<nuclei.size()-1;i++)
2088 printf(" %3i%-2s",(int) i+1,nuclei[i].symbol.c_str());
2089 printf("\n");
2090
2091 // Print atomic entries
2092 for(size_t i=1;i<nuclei.size();i++) {
2093 printf(" %3i%-2s",(int) i+1,nuclei[i].symbol.c_str());
2094 for(size_t j=0;j<i;j++)
2095 printf(" %5.3f",norm(nuclei[i].r-nuclei[j].r)/ANGSTROMINBOHR);
2096 printf("\n");
2097 }
2098 }
2099
2100 printf("\nList of basis functions:\n");
2101
2102 if(verbose) {
2103 for(size_t i=0;i<shells.size();i++) {
2104 printf("Shell %4i",(int) i);
2105 shells[i].print();
2106 }
2107 } else {
2108 for(size_t i=0;i<shells.size();i++) {
2109 // Type of shell - spherical harmonics or cartesians
2110 std::string type;
2111 if(shells[i].lm_in_use())
2112 type="sph";
2113 else
2114 type="cart";
2115
2116
2117 printf("Shell %4i",(int) i+1);
2118 printf("\t%c %4s shell at nucleus %3i with with basis functions %4i-%-4i\n",shell_types[shells[i].get_am()],type.c_str(),(int) (shells[i].get_center_ind()+1),(int) shells[i].get_first_ind()+1,(int) shells[i].get_last_ind()+1);
2119 }
2120 }
2121
2122
2123 printf("\nBasis set contains %i functions, maximum angular momentum is %i.\\
2124 n",(int) get_Nbf(),get_max_am());
2125 if(is_lm_default())
2126 printf("Spherical harmonic Gaussians are used by default, there are %i cartesians.\n",(int) get_Ncart());
2127 else
2128 printf("Cartesian Gaussians are used by default.\n");
2129 }
2130
cart_to_sph_trans() const2131 arma::mat BasisSet::cart_to_sph_trans() const {
2132 // Form transformation matrix to spherical harmonics
2133
2134 const size_t Nlm=get_Nlm();
2135 const size_t Ncart=get_Ncart();
2136
2137 // Returned matrix
2138 arma::mat trans(Nlm,Ncart);
2139 trans.zeros();
2140
2141 // Bookkeeping indices
2142 size_t n=0, l=0;
2143
2144 // Helper matrix
2145 arma::mat tmp;
2146
2147 for(size_t i=0;i<shells.size();i++) {
2148 // Get angular momentum of shell
2149 int am=shells[i].get_am();
2150
2151 // Number of cartesians and harmonics on shell
2152 int Nc=(am+1)*(am+2)/2;
2153 int Nl=2*am+1;
2154
2155 // Get transformation matrix
2156 tmp=Ylm_transmat(am);
2157
2158 // Store transformation matrix
2159 trans.submat(l,n,l+Nl-1,n+Nc-1)=tmp;
2160 n+=Nc;
2161 l+=Nl;
2162 }
2163
2164 return trans;
2165 }
2166
sph_to_cart_trans() const2167 arma::mat BasisSet::sph_to_cart_trans() const {
2168 // Form transformation matrix to cartesians
2169
2170 return inv(cart_to_sph_trans());
2171 }
2172
2173
overlap() const2174 arma::mat BasisSet::overlap() const {
2175 // Form overlap matrix
2176
2177 // Size of basis set
2178 const size_t N=get_Nbf();
2179
2180 // Initialize matrix
2181 arma::mat S(N,N);
2182 S.zeros();
2183
2184 // Loop over shells
2185 #ifdef _OPENMP
2186 #pragma omp parallel for schedule(dynamic)
2187 #endif
2188 for(size_t ip=0;ip<shellpairs.size();ip++) {
2189 // Shells in pair
2190 size_t i=shellpairs[ip].is;
2191 size_t j=shellpairs[ip].js;
2192
2193 // Get overlap between shells
2194 arma::mat tmp=shells[i].overlap(shells[j]);
2195
2196 // Store overlap
2197 S.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind())=tmp;
2198 S.submat(shells[j].get_first_ind(),shells[i].get_first_ind(),shells[j].get_last_ind(),shells[i].get_last_ind())=arma::trans(tmp);
2199 }
2200
2201 return S;
2202 }
2203
coulomb_overlap() const2204 arma::mat BasisSet::coulomb_overlap() const {
2205 // Form overlap matrix
2206
2207 // Size of basis set
2208 const size_t N=get_Nbf();
2209
2210 // Initialize matrix
2211 arma::mat S(N,N);
2212 S.zeros();
2213
2214 // Loop over shells
2215 #ifdef _OPENMP
2216 #pragma omp parallel for schedule(dynamic)
2217 #endif
2218 for(size_t ip=0;ip<shellpairs.size();ip++) {
2219 // Shells in pair
2220 size_t i=shellpairs[ip].is;
2221 size_t j=shellpairs[ip].js;
2222
2223 arma::mat tmp=shells[i].coulomb_overlap(shells[j]);
2224
2225 // Store overlap
2226 S.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind())=tmp;
2227 S.submat(shells[j].get_first_ind(),shells[i].get_first_ind(),shells[j].get_last_ind(),shells[i].get_last_ind())=arma::trans(tmp);
2228 }
2229
2230 return S;
2231 }
2232
overlap(const BasisSet & rhs) const2233 arma::mat BasisSet::overlap(const BasisSet & rhs) const {
2234 // Form overlap wrt to other basis set
2235
2236 // Size of this basis set
2237 const size_t Nl=get_Nbf();
2238 // Size of rhs basis
2239 const size_t Nr=rhs.get_Nbf();
2240
2241 // Initialize matrix
2242 arma::mat S12(Nl,Nr);
2243 S12.zeros();
2244
2245 // Loop over shells
2246 #ifdef _OPENMP
2247 #pragma omp parallel for schedule(dynamic)
2248 #endif
2249 for(size_t i=0;i<shells.size();i++) {
2250 for(size_t j=0;j<rhs.shells.size();j++) {
2251 S12.submat(shells[i].get_first_ind(),rhs.shells[j].get_first_ind(),
2252 shells[i].get_last_ind() ,rhs.shells[j].get_last_ind() )=shells[i].overlap(rhs.shells[j]);;
2253 }
2254 }
2255 return S12;
2256 }
2257
coulomb_overlap(const BasisSet & rhs) const2258 arma::mat BasisSet::coulomb_overlap(const BasisSet & rhs) const {
2259 // Form overlap wrt to other basis set
2260
2261 // Size of this basis set
2262 const size_t Nl=get_Nbf();
2263 // Size of rhs basis
2264 const size_t Nr=rhs.get_Nbf();
2265
2266 // Initialize matrix
2267 arma::mat S12(Nl,Nr);
2268 S12.zeros();
2269
2270 // Loop over shells
2271 #ifdef _OPENMP
2272 #pragma omp parallel for schedule(dynamic)
2273 #endif
2274 for(size_t i=0;i<shells.size();i++) {
2275 for(size_t j=0;j<rhs.shells.size();j++) {
2276 S12.submat(shells[i].get_first_ind(),rhs.shells[j].get_first_ind(),
2277 shells[i].get_last_ind() ,rhs.shells[j].get_last_ind() )=shells[i].coulomb_overlap(rhs.shells[j]);;
2278 }
2279 }
2280 return S12;
2281 }
2282
2283
kinetic() const2284 arma::mat BasisSet::kinetic() const {
2285 // Form kinetic energy matrix
2286
2287 // Size of basis set
2288 size_t N=get_Nbf();
2289
2290 // Initialize matrix
2291 arma::mat T(N,N);
2292 T.zeros();
2293
2294 // Loop over shells
2295 #ifdef _OPENMP
2296 #pragma omp parallel for schedule(dynamic)
2297 #endif
2298 for(size_t ip=0;ip<shellpairs.size();ip++) {
2299 // Shells in pair
2300 size_t i=shellpairs[ip].is;
2301 size_t j=shellpairs[ip].js;
2302
2303 // Get partial kinetic energy matrix
2304 arma::mat tmp=shells[i].kinetic(shells[j]);
2305
2306 // Store result
2307 T.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind())=tmp;
2308 T.submat(shells[j].get_first_ind(),shells[i].get_first_ind(),shells[j].get_last_ind(),shells[i].get_last_ind())=arma::trans(tmp);
2309 }
2310
2311 return T;
2312 }
2313
nuclear() const2314 arma::mat BasisSet::nuclear() const {
2315 // Form nuclear attraction matrix
2316
2317 // Size of basis set
2318 size_t N=get_Nbf();
2319
2320 // Initialize matrix
2321 arma::mat Vnuc(N,N);
2322 Vnuc.zeros();
2323
2324 // Loop over shells
2325 #ifdef _OPENMP
2326 #pragma omp parallel for schedule(dynamic)
2327 #endif
2328 for(size_t ip=0;ip<shellpairs.size();ip++)
2329 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
2330 // If BSSE nucleus, do nothing
2331 if(nuclei[inuc].bsse)
2332 continue;
2333
2334 // Nuclear charge
2335 int Z=nuclei[inuc].Z;
2336
2337 // Coordinates of nucleus
2338 double cx=nuclei[inuc].r.x;
2339 double cy=nuclei[inuc].r.y;
2340 double cz=nuclei[inuc].r.z;
2341
2342 // Shells in pair
2343 size_t i=shellpairs[ip].is;
2344 size_t j=shellpairs[ip].js;
2345
2346 // Get subblock
2347 arma::mat tmp=Z*shells[i].nuclear(cx,cy,cz,shells[j]);
2348
2349 // On the off diagonal we fill out both sides of the matrix
2350 if(i!=j) {
2351 Vnuc.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind())+=tmp;
2352 Vnuc.submat(shells[j].get_first_ind(),shells[i].get_first_ind(),shells[j].get_last_ind(),shells[i].get_last_ind())+=arma::trans(tmp);
2353 } else
2354 // On the diagonal we just get it once
2355 Vnuc.submat(shells[i].get_first_ind(),shells[i].get_first_ind(),shells[i].get_last_ind(),shells[i].get_last_ind())+=arma::trans(tmp);
2356 }
2357
2358 return Vnuc;
2359 }
2360
potential(coords_t r) const2361 arma::mat BasisSet::potential(coords_t r) const {
2362 // Form nuclear attraction matrix
2363
2364 // Size of basis set
2365 size_t N=get_Nbf();
2366
2367 // Initialize matrix
2368 arma::mat V(N,N);
2369 V.zeros();
2370
2371 // Loop over shells
2372 #ifdef _OPENMP
2373 #pragma omp parallel for schedule(dynamic)
2374 #endif
2375 for(size_t ip=0;ip<shellpairs.size();ip++) {
2376 // Shells in pair
2377 size_t i=shellpairs[ip].is;
2378 size_t j=shellpairs[ip].js;
2379
2380 // Get subblock
2381 arma::mat tmp=shells[i].nuclear(r.x,r.y,r.z,shells[j]);
2382
2383 // On the off diagonal we fill out both sides of the matrix
2384 if(i!=j) {
2385 V.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind())=tmp;
2386 V.submat(shells[j].get_first_ind(),shells[i].get_first_ind(),shells[j].get_last_ind(),shells[i].get_last_ind())=arma::trans(tmp);
2387 } else
2388 // On the diagonal we just get it once
2389 V.submat(shells[i].get_first_ind(),shells[i].get_first_ind(),shells[i].get_last_ind(),shells[i].get_last_ind())=arma::trans(tmp);
2390 }
2391
2392 return V;
2393 }
2394
eri_screening(arma::mat & Q,arma::mat & M,double omega,double alpha,double beta) const2395 void BasisSet::eri_screening(arma::mat & Q, arma::mat & M, double omega, double alpha, double beta) const {
2396 // Get unique pairs
2397 std::vector<shellpair_t> pairs=get_unique_shellpairs();
2398
2399 Q.zeros(shells.size(),shells.size());
2400 M.zeros(shells.size(),shells.size());
2401
2402 #ifdef _OPENMP
2403 #pragma omp parallel
2404 #endif
2405 {
2406 ERIWorker *eri;
2407 const std::vector<double> * erip;
2408
2409 if(omega==0.0 && alpha==1.0 && beta==0.0)
2410 eri=new ERIWorker(get_max_am(),get_max_Ncontr());
2411 else
2412 eri=new ERIWorker_srlr(get_max_am(),get_max_Ncontr(),omega,alpha,beta);
2413
2414 #ifdef _OPENMP
2415 #pragma omp for schedule(dynamic)
2416 #endif
2417 for(size_t ip=0;ip<pairs.size();ip++) {
2418 size_t i=pairs[ip].is;
2419 size_t j=pairs[ip].js;
2420
2421 // Compute (ij|ij) integrals
2422 {
2423 eri->compute(&shells[i],&shells[j],&shells[i],&shells[j]);
2424 erip=eri->getp();
2425 // Get maximum value
2426 double m=0.0;
2427 for(size_t k=0;k<erip->size();k++)
2428 m=std::max(m,std::abs((*erip)[k]));
2429 m=sqrt(m);
2430 Q(i,j)=m;
2431 Q(j,i)=m;
2432 }
2433
2434 // Compute (ii|jj) integrals
2435 {
2436 eri->compute(&shells[i],&shells[i],&shells[j],&shells[j]);
2437 erip=eri->getp();
2438 // Get maximum value
2439 double m=0.0;
2440 for(size_t k=0;k<erip->size();k++)
2441 m=std::max(m,std::abs((*erip)[k]));
2442 m=sqrt(m);
2443 M(i,j)=m;
2444 M(j,i)=m;
2445 }
2446 }
2447
2448 delete eri;
2449 }
2450 }
2451
nuclear_pulay(const arma::mat & P) const2452 arma::vec BasisSet::nuclear_pulay(const arma::mat & P) const {
2453 arma::vec f(3*nuclei.size());
2454 f.zeros();
2455
2456 #ifdef _OPENMP
2457 #pragma omp parallel
2458 #endif
2459 {
2460 // Loop over shells
2461 #ifdef _OPENMP
2462 arma::vec fwrk(3*nuclei.size());
2463 fwrk.zeros();
2464
2465 #pragma omp for schedule(dynamic)
2466 #endif
2467 for(size_t ip=0;ip<shellpairs.size();ip++)
2468 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
2469 // If BSSE nucleus, do nothing
2470 if(nuclei[inuc].bsse)
2471 continue;
2472
2473 // Shells in pair
2474 size_t i=shellpairs[ip].is;
2475 size_t j=shellpairs[ip].js;
2476
2477 // Nuclear charge
2478 int Z=nuclei[inuc].Z;
2479
2480 // Coordinates of nucleus
2481 double cx=nuclei[inuc].r.x;
2482 double cy=nuclei[inuc].r.y;
2483 double cz=nuclei[inuc].r.z;
2484
2485 // Density matrix for the pair
2486 arma::mat Pmat=P.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind());
2487
2488 // Get the forces
2489 arma::vec tmp=Z*shells[i].nuclear_pulay(cx,cy,cz,Pmat,shells[j]);
2490
2491 // Off-diagonal?
2492 if(i!=j)
2493 tmp*=2.0;
2494
2495 // and increment the nuclear force.
2496 #ifdef _OPENMP
2497 fwrk.subvec(3*shells[i].get_center_ind(),3*shells[i].get_center_ind()+2)+=tmp.subvec(0,2);
2498 fwrk.subvec(3*shells[j].get_center_ind(),3*shells[j].get_center_ind()+2)+=tmp.subvec(3,5);
2499 #else
2500 f.subvec(3*shells[i].get_center_ind(),3*shells[i].get_center_ind()+2)+=tmp.subvec(0,2);
2501 f.subvec(3*shells[j].get_center_ind(),3*shells[j].get_center_ind()+2)+=tmp.subvec(3,5);
2502 #endif
2503 }
2504
2505 #ifdef _OPENMP
2506 #pragma omp critical
2507 f+=fwrk;
2508 #endif
2509 }
2510
2511 return f;
2512 }
2513
nuclear_der(const arma::mat & P) const2514 arma::vec BasisSet::nuclear_der(const arma::mat & P) const {
2515 arma::vec f(3*nuclei.size());
2516 f.zeros();
2517
2518 #ifdef _OPENMP
2519 #pragma omp parallel
2520 #endif
2521 {
2522 // Loop over shells
2523 #ifdef _OPENMP
2524 arma::vec fwrk(3*nuclei.size());
2525 fwrk.zeros();
2526
2527 #pragma omp for schedule(dynamic)
2528 #endif
2529 for(size_t ip=0;ip<shellpairs.size();ip++)
2530 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
2531 // If BSSE nucleus, do nothing
2532 if(nuclei[inuc].bsse)
2533 continue;
2534
2535 // Shells in pair
2536 size_t i=shellpairs[ip].is;
2537 size_t j=shellpairs[ip].js;
2538
2539 // Nuclear charge
2540 int Z=nuclei[inuc].Z;
2541
2542 // Coordinates of nucleus
2543 double cx=nuclei[inuc].r.x;
2544 double cy=nuclei[inuc].r.y;
2545 double cz=nuclei[inuc].r.z;
2546
2547 // Density matrix for the pair
2548 arma::mat Pmat=P.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind());
2549
2550 // Get the forces
2551 arma::vec tmp=Z*shells[i].nuclear_der(cx,cy,cz,Pmat,shells[j]);
2552
2553 // Off-diagonal?
2554 if(i!=j)
2555 tmp*=2.0;
2556
2557 // and increment the nuclear force.
2558 #ifdef _OPENMP
2559 fwrk.subvec(3*inuc,3*inuc+2)+=tmp;
2560 #else
2561 f.subvec(3*inuc,3*inuc+2)+=tmp;
2562 #endif
2563 }
2564
2565 #ifdef _OPENMP
2566 #pragma omp critical
2567 f+=fwrk;
2568 #endif
2569 }
2570
2571 return f;
2572 }
2573
kinetic_pulay(const arma::mat & P) const2574 arma::vec BasisSet::kinetic_pulay(const arma::mat & P) const {
2575 arma::vec f(3*nuclei.size());
2576 f.zeros();
2577
2578 #ifdef _OPENMP
2579 #pragma omp parallel
2580 #endif
2581 {
2582 // Loop over shells
2583 #ifdef _OPENMP
2584 arma::vec fwrk(3*nuclei.size());
2585 fwrk.zeros();
2586
2587 #pragma omp for schedule(dynamic)
2588 #endif
2589 for(size_t ip=0;ip<shellpairs.size();ip++) {
2590 // Shells in pair
2591 size_t i=shellpairs[ip].is;
2592 size_t j=shellpairs[ip].js;
2593
2594 // Density matrix for the pair
2595 arma::mat Pmat=P.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind());
2596
2597 // Get the forces
2598 arma::vec tmp=shells[i].kinetic_pulay(Pmat,shells[j]);
2599
2600 // Off-diagonal?
2601 if(i!=j)
2602 tmp*=2.0;
2603
2604 // and increment the nuclear force.
2605 #ifdef _OPENMP
2606 fwrk.subvec(3*shells[i].get_center_ind(),3*shells[i].get_center_ind()+2)+=tmp.subvec(0,2);
2607 fwrk.subvec(3*shells[j].get_center_ind(),3*shells[j].get_center_ind()+2)+=tmp.subvec(3,5);
2608 #else
2609 f.subvec(3*shells[i].get_center_ind(),3*shells[i].get_center_ind()+2)+=tmp.subvec(0,2);
2610 f.subvec(3*shells[j].get_center_ind(),3*shells[j].get_center_ind()+2)+=tmp.subvec(3,5);
2611 #endif
2612 }
2613
2614 #ifdef _OPENMP
2615 #pragma omp critical
2616 f+=fwrk;
2617 #endif
2618 }
2619
2620 return f;
2621 }
2622
overlap_der(const arma::mat & P) const2623 arma::vec BasisSet::overlap_der(const arma::mat & P) const {
2624 arma::vec f(3*nuclei.size());
2625 f.zeros();
2626
2627 #ifdef _OPENMP
2628 #pragma omp parallel
2629 #endif
2630 {
2631 // Loop over shells
2632 #ifdef _OPENMP
2633 arma::vec fwrk(3*nuclei.size());
2634 fwrk.zeros();
2635
2636 #pragma omp for schedule(dynamic)
2637 #endif
2638 for(size_t ip=0;ip<shellpairs.size();ip++) {
2639
2640 // Shells in pair
2641 size_t i=shellpairs[ip].is;
2642 size_t j=shellpairs[ip].js;
2643
2644 // Density matrix for the pair
2645 arma::mat Pmat=P.submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind());
2646
2647 // Get the forces
2648 arma::vec tmp=shells[i].overlap_der(Pmat,shells[j]);
2649
2650 // Off-diagonal?
2651 if(i!=j)
2652 tmp*=2.0;
2653
2654 // and increment the nuclear force.
2655 #ifdef _OPENMP
2656 fwrk.subvec(3*shells[i].get_center_ind(),3*shells[i].get_center_ind()+2)+=tmp.subvec(0,2);
2657 fwrk.subvec(3*shells[j].get_center_ind(),3*shells[j].get_center_ind()+2)+=tmp.subvec(3,5);
2658 #else
2659 f.subvec(3*shells[i].get_center_ind(),3*shells[i].get_center_ind()+2)+=tmp.subvec(0,2);
2660 f.subvec(3*shells[j].get_center_ind(),3*shells[j].get_center_ind()+2)+=tmp.subvec(3,5);
2661 #endif
2662 }
2663
2664 #ifdef _OPENMP
2665 #pragma omp critical
2666 f+=fwrk;
2667 #endif
2668 }
2669
2670 return f;
2671 }
2672
nuclear_force() const2673 arma::vec BasisSet::nuclear_force() const {
2674 arma::vec f(3*nuclei.size());
2675 f.zeros();
2676
2677 for(size_t i=0;i<nuclei.size();i++) {
2678 if(nuclei[i].bsse)
2679 continue;
2680
2681 for(size_t j=0;j<i;j++) {
2682 if(nuclei[j].bsse)
2683 continue;
2684
2685 // Calculate distance
2686 coords_t rij=nuclei[i].r-nuclei[j].r;
2687 // and its third power
2688 double rcb=pow(norm(rij),3);
2689
2690 // Force is
2691 arma::vec F(3);
2692 F(0)=nuclei[i].Z*nuclei[j].Z/rcb*rij.x;
2693 F(1)=nuclei[i].Z*nuclei[j].Z/rcb*rij.y;
2694 F(2)=nuclei[i].Z*nuclei[j].Z/rcb*rij.z;
2695
2696 f.subvec(3*i,3*i+2)+=F;
2697 f.subvec(3*j,3*j+2)-=F;
2698 }
2699 }
2700
2701 return f;
2702 }
2703
moment(int mom,double x,double y,double z) const2704 std::vector<arma::mat> BasisSet::moment(int mom, double x, double y, double z) const {
2705 // Compute moment integrals around (x,y,z);
2706
2707 // Number of moments to compute is
2708 size_t Nmom=(mom+1)*(mom+2)/2;
2709 // Amount of basis functions is
2710 size_t Nbf=get_Nbf();
2711
2712 // Returned array, holding the moment integrals
2713 std::vector<arma::mat> ret;
2714 ret.reserve(Nmom);
2715
2716 // Initialize arrays
2717 for(size_t i=0;i<Nmom;i++) {
2718 ret.push_back(arma::mat(Nbf,Nbf));
2719 ret[i].zeros();
2720 }
2721
2722 // Loop over shells
2723 #ifdef _OPENMP
2724 #pragma omp parallel for schedule(dynamic)
2725 #endif
2726 for(size_t ip=0;ip<shellpairs.size();ip++) {
2727 // Shells in pair
2728 size_t i=shellpairs[ip].is;
2729 size_t j=shellpairs[ip].js;
2730
2731
2732 // Compute moment integral over shells
2733 std::vector<arma::mat> ints=shells[i].moment(mom,x,y,z,shells[j]);
2734
2735 // Store moments
2736 if(i!=j) {
2737 for(size_t m=0;m<Nmom;m++) {
2738 ret[m].submat(shells[i].get_first_ind(),shells[j].get_first_ind(),shells[i].get_last_ind(),shells[j].get_last_ind())=ints[m];
2739 ret[m].submat(shells[j].get_first_ind(),shells[i].get_first_ind(),shells[j].get_last_ind(),shells[i].get_last_ind())=arma::trans(ints[m]);
2740 }
2741 } else {
2742 for(size_t m=0;m<Nmom;m++)
2743 ret[m].submat(shells[i].get_first_ind(),shells[i].get_first_ind(),shells[i].get_last_ind(),shells[i].get_last_ind())=ints[m];
2744 }
2745 }
2746
2747 return ret;
2748 }
2749
integral() const2750 arma::vec BasisSet::integral() const {
2751 arma::vec ints(get_Nbf());
2752 #ifdef _OPENMP
2753 #pragma omp parallel for
2754 #endif
2755 for(size_t is=0;is<shells.size();is++)
2756 ints.subvec(shells[is].get_first_ind(),shells[is].get_last_ind())=shells[is].integral();
2757
2758 return ints;
2759 }
2760
three_overlap(const GaussianShell * is,const GaussianShell * js,const GaussianShell * ks)2761 arma::cube three_overlap(const GaussianShell *is, const GaussianShell *js, const GaussianShell*ks) {
2762 // First, compute the cartesian integrals. Centers of shells
2763 coords_t icen=is->get_center();
2764 coords_t jcen=js->get_center();
2765 coords_t kcen=ks->get_center();
2766
2767 // Cartesian functions
2768 std::vector<shellf_t> icart=is->get_cart();
2769 std::vector<shellf_t> jcart=js->get_cart();
2770 std::vector<shellf_t> kcart=ks->get_cart();
2771
2772 // Exponential contractions
2773 std::vector<contr_t> icontr=is->get_contr();
2774 std::vector<contr_t> jcontr=js->get_contr();
2775 std::vector<contr_t> kcontr=ks->get_contr();
2776
2777 // Cartesian integrals
2778 arma::cube cartint(icart.size(),jcart.size(),kcart.size());
2779 cartint.zeros();
2780 for(size_t ix=0;ix<icontr.size();ix++) {
2781 double iz=icontr[ix].z;
2782 double ic=icontr[ix].c;
2783
2784 for(size_t jx=0;jx<jcontr.size();jx++) {
2785 double jz=jcontr[jx].z;
2786 double jc=jcontr[jx].c;
2787
2788 for(size_t kx=0;kx<kcontr.size();kx++) {
2789 double kz=kcontr[kx].z;
2790 double kc=kcontr[kx].c;
2791
2792 cartint+=ic*jc*kc*three_overlap_int_os(icen.x,icen.y,icen.z,jcen.x,jcen.y,jcen.z,kcen.x,kcen.y,kcen.z,iz,jz,kz,icart,jcart,kcart);
2793 }
2794 }
2795 }
2796
2797 // Convert first two indices to spherical basis
2798 arma::cube twints(is->get_Nbf(),js->get_Nbf(),kcart.size());
2799 twints.zeros();
2800 for(size_t ik=0;ik<kcart.size();ik++) {
2801 // ({i}|{j}|ks) is
2802 arma::mat momval=cartint.slice(ik);
2803
2804 // Do conversion
2805 if(is->lm_in_use())
2806 momval=is->get_trans()*momval;
2807 if(js->lm_in_use())
2808 momval=momval*arma::trans(js->get_trans());
2809
2810 twints.slice(ik)=momval;
2811 }
2812
2813 // Convert last index to spherical basis
2814 if(! ks->lm_in_use())
2815 return twints;
2816
2817 // Transformation matrix
2818 arma::mat ktrans=ks->get_trans();
2819
2820 // Final integrals
2821 arma::cube ints(is->get_Nbf(),js->get_Nbf(),ks->get_Nbf());
2822 ints.zeros();
2823
2824 // Loop over spherical basis functions
2825 for(size_t ik=0;ik<ks->get_Nbf();ik++)
2826 // Loop over cartesians
2827 for(size_t ick=0;ick<kcart.size();ick++)
2828 ints.slice(ik)+=ktrans(ik,ick)*twints.slice(ick);
2829
2830 return ints;
2831 }
2832
2833
Ztot() const2834 int BasisSet::Ztot() const {
2835 int Zt=0;
2836 for(size_t i=0;i<nuclei.size();i++) {
2837 if(nuclei[i].bsse)
2838 continue;
2839 Zt+=nuclei[i].Z;
2840 }
2841 return Zt;
2842 }
2843
Enuc() const2844 double BasisSet::Enuc() const {
2845 double En=0.0;
2846
2847 for(size_t i=0;i<nuclei.size();i++) {
2848 if(nuclei[i].bsse)
2849 continue;
2850
2851 int Zi=nuclei[i].Z;
2852
2853 for(size_t j=0;j<i;j++) {
2854 if(nuclei[j].bsse)
2855 continue;
2856 int Zj=nuclei[j].Z;
2857
2858 En+=Zi*Zj/nucleardist(i,j);
2859 }
2860 }
2861
2862 return En;
2863 }
2864
projectMOs(const BasisSet & oldbas,const arma::vec & oldE,const arma::mat & oldMOs,arma::vec & E,arma::mat & MOs,size_t nocc) const2865 void BasisSet::projectMOs(const BasisSet & oldbas, const arma::vec & oldE, const arma::mat & oldMOs, arma::vec & E, arma::mat & MOs, size_t nocc) const {
2866 if(*this == oldbas) {
2867 MOs=oldMOs;
2868 E=oldE;
2869 return;
2870 }
2871
2872 if(oldMOs.n_cols < nocc) {
2873 oldbas.print();
2874 fflush(stdout);
2875
2876 std::ostringstream oss;
2877 oss << "Old basis doesn't have enough occupied orbitals: " << oldbas.get_Nbf() << " basis functions but " << nocc << " orbitals wanted!\n";
2878 throw std::runtime_error(oss.str());
2879 }
2880
2881 BasisSet transbas(oldbas);
2882 transbas.nuclei=nuclei;
2883 // Store new geometries
2884 for(size_t i=0;i<oldbas.shells.size();i++) {
2885 // Index of center in the old basis is
2886 size_t idx=oldbas.shells[i].get_center_ind();
2887 // Set new coordinates
2888 transbas.shells[i].set_center(nuclei[idx].r,idx);
2889 }
2890 transbas.finalize(false,false);
2891
2892 // Get overlap matrix
2893 arma::mat S11=overlap();
2894 // and overlap with old basis
2895 arma::mat S12=overlap(transbas);
2896
2897 // Form orthogonalizing matrix
2898 arma::mat Svec;
2899 arma::vec Sval;
2900 eig_sym_ordered(Sval,Svec,S11);
2901
2902 // Get number of basis functions
2903 const size_t Nbf=get_Nbf();
2904
2905 // Count number of eigenvalues that are above cutoff
2906 size_t Nind=0;
2907 for(size_t i=0;i<Nbf;i++)
2908 if(Sval(i)>=settings.get_double("LinDepThresh"))
2909 Nind++;
2910 // Number of linearly dependent basis functions
2911 const size_t Ndep=Nbf-Nind;
2912 if(Nind<nocc) {
2913 print();
2914 fflush(stdout);
2915
2916 std::ostringstream oss;
2917 oss << "Basis set too small for occupied orbitals: " << Nind << " independent functions but " << nocc << " orbitals!\n";
2918 throw std::runtime_error(oss.str());
2919 }
2920
2921 // Get rid of linearly dependent eigenvalues and eigenvectors
2922 Sval=Sval.subvec(Ndep,Nbf-1);
2923 Svec=Svec.cols(Ndep,Nbf-1);
2924
2925 // Form canonical orthonormalization matrix
2926 arma::mat Sinvh(Nbf,Nind);
2927 for(size_t i=0;i<Nind;i++)
2928 Sinvh.col(i)=Svec.col(i)/sqrt(Sval(i));
2929
2930 // and the real S^-1
2931 arma::mat Sinv=Sinvh*arma::trans(Sinvh);
2932
2933 // New orbitals and orbital energies
2934 MOs.zeros(Sinvh.n_rows,Nind);
2935 E.zeros(Nind);
2936
2937 if(!nocc) {
2938 MOs=Sinvh;
2939 return;
2940 }
2941
2942 // Projected orbitals
2943 MOs.cols(0,nocc-1)=Sinv*S12*oldMOs.cols(0,nocc-1);
2944 // and energies. PZ calculations might not have energies stored!
2945 size_t nE=std::min((arma::uword) nocc,oldE.n_elem);
2946 if(nE>0)
2947 E.subvec(0,nE-1)=oldE.subvec(0,nE-1);
2948 // Overlap of projected orbitals is
2949 arma::mat SMO=arma::trans(MOs.cols(0,nocc-1))*S11*MOs.cols(0,nocc-1);
2950
2951 // Do eigendecomposition
2952 arma::mat SMOvec;
2953 arma::vec SMOval;
2954 bool ok=arma::eig_sym(SMOval,SMOvec,SMO);
2955 if(!ok)
2956 throw std::runtime_error("Failed to diagonalize orbital overlap\n");
2957
2958 // Orthogonalizing matrix is
2959 arma::mat orthmat=SMOvec*arma::diagmat(1.0/sqrt(SMOval))*trans(SMOvec);
2960
2961 // Orthonormal projected orbitals are
2962 MOs.cols(0,nocc-1)=MOs.cols(0,nocc-1)*orthmat;
2963
2964 // Form virtual orbitals
2965 if(nocc<Nind) {
2966 // MO submatrix
2967 arma::mat C=MOs.cols(0,nocc-1);
2968
2969 // Generate other vectors. Compute overlap of functions
2970 arma::mat X=arma::trans(Sinvh)*S11*C;
2971 // and perform SVD
2972 arma::mat U, V;
2973 arma::vec s;
2974 bool svdok=arma::svd(U,s,V,X);
2975 if(!svdok)
2976 throw std::runtime_error("SVD decomposition failed!\n");
2977
2978 // Rotate eigenvectors.
2979 arma::mat Cnew(Sinvh*U);
2980
2981 // Now, the occupied subspace is found in the first nocc
2982 // eigenvectors.
2983 MOs.cols(nocc,Nind-1)=Cnew.cols(nocc,Nind-1);
2984 // Dummy energies
2985 if(nE==nocc)
2986 E.subvec(nocc,Nind-1)=1.1*std::max(E(nocc-1),0.0)*arma::ones(Nind-nocc,1);
2987 }
2988
2989 // Failsafe
2990 try {
2991 // Check orthogonality of orbitals
2992 check_orth(MOs,S11,false);
2993 } catch(std::runtime_error & err) {
2994 std::ostringstream oss;
2995 oss << "Orbitals generated by MO_project are not orthonormal.\n";
2996 throw std::runtime_error(oss.str());
2997 }
2998 }
2999
projectOMOs(const BasisSet & oldbas,const arma::cx_mat & oldOMOs,arma::cx_mat & OMOs,size_t nocc) const3000 void BasisSet::projectOMOs(const BasisSet & oldbas, const arma::cx_mat & oldOMOs, arma::cx_mat & OMOs, size_t nocc) const {
3001 if(*this == oldbas) {
3002 OMOs=oldOMOs;
3003 return;
3004 }
3005
3006 if(oldOMOs.n_cols < nocc) {
3007 oldbas.print();
3008 fflush(stdout);
3009
3010 std::ostringstream oss;
3011 oss << "Old basis doesn't have enough occupied orbitals: " << oldbas.get_Nbf() << " basis functions but " << nocc << " orbitals wanted!\n";
3012 throw std::runtime_error(oss.str());
3013 }
3014
3015 BasisSet transbas(oldbas);
3016 transbas.nuclei=nuclei;
3017 // Store new geometries
3018 for(size_t i=0;i<oldbas.shells.size();i++) {
3019 // Index of center in the old basis is
3020 size_t idx=oldbas.shells[i].get_center_ind();
3021 // Set new coordinates
3022 transbas.shells[i].set_center(nuclei[idx].r,idx);
3023 }
3024 transbas.finalize(false,false);
3025
3026 // Get overlap matrix
3027 arma::mat S11=overlap();
3028 // and overlap with old basis
3029 arma::mat S12=overlap(transbas);
3030
3031 // Form orthogonalizing matrix
3032 arma::mat Svec;
3033 arma::vec Sval;
3034 eig_sym_ordered(Sval,Svec,S11);
3035
3036 // Get number of basis functions
3037 const size_t Nbf=get_Nbf();
3038
3039 // Count number of eigenvalues that are above cutoff
3040 size_t Nind=0;
3041 for(size_t i=0;i<Nbf;i++)
3042 if(Sval(i)>=settings.get_double("LinDepThresh"))
3043 Nind++;
3044 // Number of linearly dependent basis functions
3045 const size_t Ndep=Nbf-Nind;
3046 if(Nind<nocc) {
3047 print();
3048 fflush(stdout);
3049
3050 std::ostringstream oss;
3051 oss << "Basis set too small for occupied orbitals: " << Nind << " independent functions but " << nocc << " orbitals!\n";
3052 throw std::runtime_error(oss.str());
3053 }
3054
3055 // Get rid of linearly dependent eigenvalues and eigenvectors
3056 Sval=Sval.subvec(Ndep,Nbf-1);
3057 Svec=Svec.cols(Ndep,Nbf-1);
3058
3059 // Form canonical orthonormalization matrix
3060 arma::mat Sinvh(Nbf,Nind);
3061 for(size_t i=0;i<Nind;i++)
3062 Sinvh.col(i)=Svec.col(i)/sqrt(Sval(i));
3063
3064 // and the real S^-1
3065 arma::mat Sinv=Sinvh*arma::trans(Sinvh);
3066
3067 // New orbitals and orbital energies
3068 OMOs.zeros(Sinvh.n_rows,Nind);
3069
3070 if(!nocc) {
3071 OMOs=Sinvh*COMPLEX1;
3072 return;
3073 }
3074
3075 // Projected orbitals
3076 OMOs.cols(0,nocc-1)=Sinv*S12*oldOMOs.cols(0,nocc-1);
3077
3078 // Overlap of projected orbitals is
3079 arma::cx_mat SMO=arma::trans(OMOs.cols(0,nocc-1))*S11*OMOs.cols(0,nocc-1);
3080
3081 // Do eigendecomposition
3082 arma::cx_mat SMOvec;
3083 arma::vec SMOval;
3084 bool ok=arma::eig_sym(SMOval,SMOvec,SMO);
3085 if(!ok)
3086 throw std::runtime_error("Failed to diagonalize orbital overlap\n");
3087
3088 // Orthogonalizing matrix is
3089 arma::cx_mat orthmat=SMOvec*arma::diagmat(1.0/sqrt(SMOval))*trans(SMOvec);
3090
3091 // Orthonormal projected orbitals are
3092 OMOs.cols(0,nocc-1)=OMOs.cols(0,nocc-1)*orthmat;
3093
3094 // Form virtual orbitals
3095 if(nocc<Nind) {
3096 // MO submatrix
3097 arma::cx_mat C=OMOs.cols(0,nocc-1);
3098
3099 // Generate other vectors. Compute overlap of functions
3100 arma::cx_mat X=arma::trans(Sinvh)*S11*C;
3101 // and perform SVD
3102 arma::cx_mat U, V;
3103 arma::vec s;
3104 bool svdok=arma::svd(U,s,V,X);
3105 if(!svdok)
3106 throw std::runtime_error("SVD decomposition failed!\n");
3107
3108 // Rotate eigenvectors.
3109 arma::cx_mat Cnew(Sinvh*U);
3110
3111 // Now, the occupied subspace is found in the first nocc
3112 // eigenvectors.
3113 OMOs.cols(nocc,Nind-1)=Cnew.cols(nocc,Nind-1);
3114 }
3115
3116 // Failsafe
3117 try {
3118 // Check orthogonality of orbitals
3119 check_orth(OMOs,S11,false);
3120 } catch(std::runtime_error & err) {
3121 std::ostringstream oss;
3122 oss << "Orbitals generated by OMO_project are not orthonormal.\n";
3123 throw std::runtime_error(oss.str());
3124 }
3125 }
3126
exponent_compare(const GaussianShell & lhs,const GaussianShell & rhs)3127 bool exponent_compare(const GaussianShell & lhs, const GaussianShell & rhs) {
3128 return lhs.get_contr()[0].z>rhs.get_contr()[0].z;
3129 }
3130
density_fitting(double fsam,int lmaxinc) const3131 BasisSet BasisSet::density_fitting(double fsam, int lmaxinc) const {
3132 // Automatically generate density fitting basis.
3133
3134 // R. Yang, A. P. Rendell and M. J. Frisch, "Automatically generated
3135 // Coulomb fitting basis sets: Design and accuracy for systems
3136 // containing H to Kr", J. Chem. Phys. 127 (2007), 074102
3137
3138 bool uselm0(settings.get_bool("UseLM"));
3139 settings.set_bool("UseLM",true);
3140 // Density fitting basis set
3141 BasisSet dfit(1);
3142 settings.set_bool("UseLM",uselm0);
3143
3144 // Loop over nuclei
3145 for(size_t in=0;in<nuclei.size();in++) {
3146 // Add nucleus to fitting set
3147 dfit.add_nucleus(nuclei[in]);
3148 // Dummy nucleus
3149 nucleus_t nuc=nuclei[in];
3150
3151 // Define lval - (1) in YRF
3152 int lval;
3153 if(nuclei[in].Z<3)
3154 lval=0;
3155 else if(nuclei[in].Z<19)
3156 lval=1;
3157 else if(nuclei[in].Z<55)
3158 lval=2;
3159 else
3160 lval=3;
3161
3162 // Get shells corresponding to this nucleus
3163 std::vector<GaussianShell> shs=get_funcs(in);
3164
3165 // Form candidate set - (2), (3) and (6) in YRF
3166 std::vector<GaussianShell> cand;
3167 for(size_t i=0;i<shs.size();i++) {
3168 // Get angular momentum
3169 int am=2*shs[i].get_am();
3170 // Get exponents
3171 std::vector<contr_t> contr=shs[i].get_contr();
3172
3173 // Dummy contraction
3174 std::vector<contr_t> C(1);
3175 C[0].c=1.0;
3176
3177 for(size_t j=0;j<contr.size();j++) {
3178 // Set exponent
3179 C[0].z=2.0*contr[j].z;
3180
3181 // Check that candidate set doesn't already contain the same function
3182 bool found=0;
3183 for(size_t k=0;k<cand.size();k++)
3184 if((cand[k].get_am()==am) && (cand[k].get_contr()[0]==C[0])) {
3185 found=1;
3186 break;
3187 }
3188
3189 // Add function
3190 if(!found) {
3191 cand.push_back(GaussianShell(am,true,C));
3192 cand[cand.size()-1].set_center(nuc.r,in);
3193 }
3194 }
3195 }
3196
3197 // Sort trial set in order of decreasing exponents (don't care
3198 // about angular momentum) - (4) in YRF
3199 std::stable_sort(cand.begin(),cand.end(),exponent_compare);
3200
3201 // Define maximum angular momentum for candidate functions and for
3202 // density fitting - (5) in YRF
3203 int lmax_obs=0;
3204 for(size_t i=0;i<shs.size();i++)
3205 if(shs[i].get_am()>lmax_obs)
3206 lmax_obs=shs[i].get_am();
3207 int lmax_abs=std::max(lmax_obs+lmaxinc,2*lval);
3208
3209 // (6) was already above.
3210
3211 while(cand.size()>0) {
3212 // Generate trial set
3213 std::vector<GaussianShell> trial;
3214
3215 // Function with largest exponent is moved to the trial set and
3216 // its exponent is set as the reference value - (7) in YRF
3217 double ref=(cand[0].get_contr())[0].z;
3218 trial.push_back(cand[0]);
3219 cand.erase(cand.begin());
3220
3221 if(cand.size()>0) {
3222 // More functions remaining, move all for which ratio of
3223 // reference to exponent is smaller than fsam - (8) in YRF
3224 for(size_t i=cand.size()-1;i<cand.size();i--)
3225 if(ref/((cand[i].get_contr())[0].z)<fsam) {
3226 trial.push_back(cand[i]);
3227 cand.erase(cand.begin()+i);
3228 }
3229
3230 // Compute geometric average of exponents - (9) in YRF
3231 double geomav=1.0;
3232 for(size_t i=0;i<trial.size();i++)
3233 geomav*=trial[i].get_contr()[0].z;
3234 geomav=pow(geomav,1.0/trial.size());
3235
3236 // printf("Geometric average of %i functions is %e.\n",(int) trial.size(),geomav);
3237
3238 // Form list of angular momentum values
3239 // Compute maximum angular moment of current trial set
3240 int ltrial=0;
3241 for(size_t i=0;i<trial.size();i++)
3242 if(trial[i].get_am()>ltrial)
3243 ltrial=trial[i].get_am();
3244
3245 // If this is larger than allowed, renormalize
3246 if(ltrial>lmax_abs)
3247 ltrial=lmax_abs;
3248
3249 // Form list of angular momentum already used in ABS
3250 std::vector<int> lvals(max_am+1);
3251 for(int i=0;i<=max_am;i++)
3252 lvals[i]=0;
3253
3254 // Maximum angular momentum of trial functions is
3255 lvals[ltrial]++;
3256 // Get shells on current center
3257 std::vector<GaussianShell> cur_shells=dfit.get_funcs(in);
3258 for(size_t i=0;i<cur_shells.size();i++)
3259 lvals[cur_shells[i].get_am()]++;
3260
3261 // Check that there are no gaps in lvals
3262 bool fill=0;
3263 for(size_t i=lvals.size()-1;i<lvals.size();i--) {
3264 // Fill down from here below
3265 if(!fill && lvals[i]>0)
3266 fill=1;
3267 if(fill && !lvals[i])
3268 lvals[i]++;
3269 }
3270
3271 // Add density fitting functions
3272 std::vector<contr_t> C(1);
3273 C[0].c=1.0;
3274 C[0].z=geomav;
3275 for(int l=0;l<=max_am;l++)
3276 if(lvals[l]>0) {
3277 // Pure spherical functions used
3278 dfit.add_shell(in,l,true,C);
3279 }
3280 }
3281 } // (10) in YRF
3282 } // (11) in YRF
3283
3284 // Normalize basis set
3285 dfit.coulomb_normalize();
3286 // Form list of unique shell pairs
3287 dfit.form_unique_shellpairs();
3288
3289 return dfit;
3290 }
3291
3292
exchange_fitting() const3293 BasisSet BasisSet::exchange_fitting() const {
3294 // Exchange fitting basis set
3295
3296 bool uselm0(settings.get_bool("UseLM"));
3297 settings.set_bool("UseLM",true);
3298 // Density fitting basis set
3299 BasisSet fit(nuclei.size());
3300 settings.set_bool("UseLM",uselm0);
3301
3302 const int maxam=get_max_am();
3303
3304 // Loop over nuclei
3305 for(size_t in=0;in<nuclei.size();in++) {
3306 // Get shells corresponding to this nucleus
3307 std::vector<GaussianShell> shs=get_funcs(in);
3308
3309 // Sort shells in increasing angular momentum
3310 std::sort(shs.begin(),shs.end());
3311
3312 // Determine amount of functions on current atom and minimum and maximum exponents
3313 std::vector<int> nfunc(2*maxam+1);
3314 std::vector<double> mine(2*maxam+1);
3315 std::vector<double> maxe(2*maxam+1);
3316 int lmax=0;
3317
3318 // Initialize arrays
3319 for(int l=0;l<=2*maxam;l++) {
3320 nfunc[l]=0;
3321 mine[l]=DBL_MAX;
3322 maxe[l]=0.0;
3323 }
3324
3325 // Loop over shells of current nucleus
3326 for(size_t ish=0;ish<shs.size();ish++)
3327 // Second loop over shells of current nucleus
3328 for(size_t jsh=0;jsh<shs.size();jsh++) {
3329
3330 // Current angular momentum
3331 int l=shs[ish].get_am()+shs[jsh].get_am();
3332
3333 // Update maximum value
3334 if(l>lmax)
3335 lmax=l;
3336
3337 // Increase amount of functions
3338 nfunc[l]++;
3339
3340 // Get exponential contractions
3341 std::vector<contr_t> icontr=shs[ish].get_contr();
3342 std::vector<contr_t> jcontr=shs[jsh].get_contr();
3343
3344 // Minimum exponent
3345 double mi=icontr[icontr.size()-1].z+jcontr[jcontr.size()-1].z;
3346 // Maximum exponent
3347 double ma=icontr[0].z+jcontr[0].z;
3348
3349 // Check global minimum and maximum
3350 if(mi<mine[l])
3351 mine[l]=mi;
3352 if(ma>maxe[l])
3353 maxe[l]=ma;
3354 }
3355
3356 // Add functions to fitting basis set
3357 for(int l=0;l<=lmax;l++) {
3358 std::vector<contr_t> C(1);
3359 C[0].c=1.0;
3360
3361 // Compute even-tempered formula
3362 double alpha=mine[l];
3363 double beta;
3364 if(nfunc[l]>1)
3365 beta=pow(maxe[l]/mine[l],1.0/(nfunc[l]-1));
3366 else
3367 beta=1.0;
3368
3369 // Add even-tempered functions
3370 for(int n=0;n<nfunc[l];n++) {
3371 // Compute exponent
3372 C[0].z=alpha*pow(beta,n);
3373 fit.add_shell(in,l,true,C);
3374 }
3375 }
3376 }
3377
3378 // Normalize basis set
3379 fit.coulomb_normalize();
3380 // Form list of unique shell pairs
3381 fit.form_unique_shellpairs();
3382
3383 return fit;
3384 }
3385
same_geometry(const BasisSet & rhs) const3386 bool BasisSet::same_geometry(const BasisSet & rhs) const {
3387 if(nuclei.size() != rhs.nuclei.size())
3388 return false;
3389
3390 for(size_t i=0;i<nuclei.size();i++)
3391 if(!(nuclei[i]==rhs.nuclei[i])) {
3392 // fprintf(stderr,"Nuclei %i differ!\n",(int) i);
3393 return false;
3394 }
3395
3396 return true;
3397 }
3398
same_shells(const BasisSet & rhs) const3399 bool BasisSet::same_shells(const BasisSet & rhs) const {
3400 if(shells.size() != rhs.shells.size())
3401 return false;
3402
3403 for(size_t i=0;i<shells.size();i++)
3404 if(!(shells[i]==rhs.shells[i])) {
3405 // fprintf(stderr,"Shells %i differ!\n",(int) i);
3406 return false;
3407 }
3408
3409 return true;
3410 }
3411
operator ==(const BasisSet & rhs) const3412 bool BasisSet::operator==(const BasisSet & rhs) const {
3413 return same_geometry(rhs) && same_shells(rhs);
3414 }
3415
decontract(arma::mat & m) const3416 BasisSet BasisSet::decontract(arma::mat & m) const {
3417 // Decontract basis set. m maps old basis functions to new ones
3418
3419 // Contraction schemes for the nuclei
3420 std::vector< std::vector<arma::mat> > coeffs(nuclei.size());
3421 std::vector< std::vector<arma::vec> > exps(nuclei.size());
3422 // Is puream used on the shell?
3423 std::vector< std::vector<bool> > puream(nuclei.size());
3424
3425 // Amount of new basis functions
3426 size_t Nbfnew=0;
3427
3428 // Collect the schemes. Loop over the nuclei.
3429 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
3430 // Construct an elemental basis set for the nucleus
3431 ElementBasisSet elbas(get_symbol(inuc));
3432
3433 // Get the shells belonging to this nucleus
3434 std::vector<GaussianShell> shs=get_funcs(inuc);
3435
3436 // and add the contractions to the elemental basis set
3437 for(size_t ish=0;ish<shs.size();ish++) {
3438 // Angular momentum is
3439 int am=shs[ish].get_am();
3440 // Normalized contraction coefficients
3441 std::vector<contr_t> c=shs[ish].get_contr_normalized();
3442 FunctionShell fsh(am,c);
3443 elbas.add_function(fsh);
3444 }
3445
3446 // Sanity check - puream must be the same for all shells of the current nucleus with the same am
3447 if(shs.size()>0) {
3448 std::vector<int> pam;
3449 for(int am=0;am<=elbas.get_max_am();am++) {
3450 // Initialization value
3451 pam.push_back(-1);
3452
3453 for(size_t ish=0;ish<shs.size();ish++) {
3454 // Skip if am is not the same
3455 if(shs[ish].get_am()!=am)
3456 continue;
3457
3458 // Is this the first shell of the type?
3459 if(pam[am]==-1)
3460 pam[am]=shs[ish].lm_in_use();
3461 else if(shs[ish].lm_in_use()!=pam[am]) {
3462 ERROR_INFO();
3463 throw std::runtime_error("BasisSet::decontract not implemented for mixed pure am on the same center.\n");
3464 }
3465 }
3466
3467 // Store the value
3468 puream[inuc].push_back(pam[am]==1);
3469 }
3470 }
3471
3472 // Exponents and contraction schemes
3473 for(int am=0;am<=elbas.get_max_am();am++) {
3474 arma::vec z;
3475 arma::mat c;
3476 elbas.get_primitives(z,c,am);
3477 coeffs[inuc].push_back(c);
3478 exps[inuc].push_back(z);
3479
3480 if(puream[inuc][am])
3481 Nbfnew+=(2*am+1)*z.size();
3482 else
3483 Nbfnew+=(am+1)*(am+2)/2*z.size();
3484 }
3485 }
3486
3487 // Now form the new, decontracted basis set.
3488 BasisSet dec;
3489 // Initialize transformation matrix
3490 m.zeros(Nbfnew,get_Nbf());
3491
3492 // Add the nuclei
3493 for(size_t i=0;i<nuclei.size();i++)
3494 dec.add_nucleus(nuclei[i]);
3495
3496 // and the shells.
3497 for(size_t inuc=0;inuc<nuclei.size();inuc++) {
3498 // Get the shells belonging to this nucleus
3499 std::vector<GaussianShell> shs=get_funcs(inuc);
3500
3501 // Generate the new basis functions. Loop over am
3502 for(int am=0;am<(int) coeffs[inuc].size();am++) {
3503 // First functions with the exponents are
3504 std::vector<size_t> ind0;
3505
3506 // Add the new shells
3507 for(size_t iz=0;iz<exps[inuc][am].size();iz++) {
3508 // Index of first function is
3509 ind0.push_back(dec.get_Nbf());
3510 // Add the shell
3511 std::vector<contr_t> hlp(1);
3512 hlp[0].c=1.0;
3513 hlp[0].z=exps[inuc][am][iz];
3514 dec.add_shell(inuc,am,puream[inuc][am],hlp,false);
3515 }
3516
3517 // and store the coefficients
3518 for(size_t ish=0;ish<shs.size();ish++)
3519 if(shs[ish].get_am()==am) {
3520 // Get the normalized contraction on the shell
3521 std::vector<contr_t> ct=shs[ish].get_contr_normalized();
3522 // and loop over the exponents
3523 for(size_t ic=0;ic<ct.size();ic++) {
3524
3525 // Find out where the exponent is in the new basis set
3526 size_t ix;
3527 for(ix=0;ix<exps[inuc][am].size();ix++)
3528 if(exps[inuc][am][ix]==ct[ic].z)
3529 // Found exponent
3530 break;
3531
3532 // Now that we know where the exponent is in the new basis
3533 // set, we can just store the coefficients. So, loop over
3534 // the functions on the shell
3535 for(size_t ibf=0;ibf<shs[ish].get_Nbf();ibf++)
3536 m(ind0[ix]+ibf,shs[ish].get_first_ind()+ibf)=ct[ic].c;
3537 }
3538 }
3539 }
3540 }
3541
3542 // Finalize the basis
3543 dec.finalize();
3544
3545 return dec;
3546 }
3547
dummyshell()3548 GaussianShell dummyshell() {
3549 // Set center
3550 coords_t r;
3551 r.x=0.0;
3552 r.y=0.0;
3553 r.z=0.0;
3554
3555 std::vector<contr_t> C(1);
3556 C[0].c=1.0;
3557 C[0].z=0.0;
3558
3559 GaussianShell sh(0,false,C);
3560 sh.set_center(r,0);
3561
3562 return sh;
3563 }
3564
i_idx(size_t N)3565 std::vector<size_t> i_idx(size_t N) {
3566 std::vector<size_t> ret;
3567 ret.reserve(N);
3568 ret.resize(N);
3569 for(size_t i=0;i<N;i++)
3570 ret[i]=(i*(i+1))/2;
3571 return ret;
3572 }
3573
construct_basis(BasisSet & basis,const std::vector<nucleus_t> & nuclei,const BasisSetLibrary & baslib)3574 void construct_basis(BasisSet & basis, const std::vector<nucleus_t> & nuclei, const BasisSetLibrary & baslib) {
3575 std::vector<atom_t> atoms(nuclei.size());
3576 for(size_t i=0;i<nuclei.size();i++) {
3577 atoms[i].x=nuclei[i].r.x;
3578 atoms[i].y=nuclei[i].r.y;
3579 atoms[i].z=nuclei[i].r.z;
3580 atoms[i].Q=nuclei[i].Q;
3581 atoms[i].num=nuclei[i].ind;
3582 atoms[i].el=nuclei[i].symbol;
3583 }
3584
3585 construct_basis(basis,atoms,baslib);
3586 }
3587
construct_basis(BasisSet & basis,const std::vector<atom_t> & atoms,const BasisSetLibrary & baslib)3588 void construct_basis(BasisSet & basis, const std::vector<atom_t> & atoms, const BasisSetLibrary & baslib) {
3589 // Number of atoms is
3590 size_t Nat=atoms.size();
3591
3592 // Indices of atoms to decontract basis set for
3593 std::vector<size_t> dec;
3594 bool decall=false;
3595 if(stricmp(settings.get_string("Decontract"),"")!=0) {
3596 // Check for '*'
3597 std::string str=settings.get_string("Decontract");
3598 if(str.size()==1 && str[0]=='*')
3599 decall=true;
3600 else
3601 // Parse and convert to C++ indexing
3602 dec=parse_range(settings.get_string("Decontract"),true);
3603 }
3604
3605 // Rotation?
3606 bool rotate=settings.get_bool("BasisRotate");
3607 double cutoff=settings.get_double("BasisCutoff");
3608
3609 // Create basis set
3610 basis=BasisSet(Nat);
3611 // and add atoms to basis set
3612 for(size_t i=0;i<Nat;i++) {
3613 // First we need to add the nucleus itself.
3614 nucleus_t nuc;
3615
3616 // Get center
3617 nuc.r.x=atoms[i].x;
3618 nuc.r.y=atoms[i].y;
3619 nuc.r.z=atoms[i].z;
3620 // Charge status
3621 nuc.Q=atoms[i].Q;
3622
3623 // Get symbol in raw form
3624 std::string el=atoms[i].el;
3625
3626 // Determine if nucleus is BSSE or not
3627 nuc.bsse=false;
3628 if(el.size()>3 && el.substr(el.size()-3,3)=="-Bq") {
3629 // Yes, this is a BSSE nucleus
3630 nuc.bsse=true;
3631 el=el.substr(0,el.size()-3);
3632 }
3633
3634 // Set symbol
3635 nuc.symbol=el;
3636 // Set charge
3637 nuc.Z=get_Z(el);
3638 // and add the nucleus.
3639 basis.add_nucleus(nuc);
3640
3641 // Now add the basis functions.
3642 ElementBasisSet elbas;
3643 try {
3644 // Check first if a special set is wanted for given center
3645 elbas=baslib.get_element(el,atoms[i].num+1);
3646 } catch(std::runtime_error & err) {
3647 // Did not find a special basis, use the general one instead.
3648 elbas=baslib.get_element(el,0);
3649 }
3650
3651 // Decontract set?
3652 bool decon=false;
3653 if(decall)
3654 // All functions decontracted
3655 decon=true;
3656 else
3657 // Check if this center is decontracted
3658 for(size_t j=0;j<dec.size();j++)
3659 if(i==dec[j])
3660 decon=true;
3661
3662 if(decon)
3663 elbas.decontract();
3664 else if(rotate)
3665 elbas.P_orthogonalize(cutoff);
3666
3667 basis.add_shells(i,elbas);
3668 }
3669
3670 // Finalize basis set and convert contractions
3671 basis.finalize(true);
3672 }
3673
compute_orbitals(const arma::mat & C,const BasisSet & bas,const coords_t & r)3674 arma::vec compute_orbitals(const arma::mat & C, const BasisSet & bas, const coords_t & r) {
3675 // Evaluate basis functions
3676 arma::vec bf=bas.eval_func(r.x,r.y,r.z);
3677
3678 // Orbitals are
3679 arma::rowvec orbs=arma::trans(bf)*C;
3680
3681 return arma::trans(orbs);
3682 }
3683
fermi_lowdin_orbitals(const arma::mat & C,const BasisSet & bas,const arma::mat & r)3684 arma::mat fermi_lowdin_orbitals(const arma::mat & C, const BasisSet & bas, const arma::mat & r) {
3685 if(r.n_cols!=3)
3686 throw std::logic_error("r should have three columns for x, y, z!\n");
3687 if(r.n_rows != C.n_cols)
3688 throw std::logic_error("r should have as many rows as there are orbitals to localize!\n");
3689 if(C.n_rows != bas.get_Nbf())
3690 throw std::logic_error("C does not correspond to basis set!\n");
3691
3692 // Evaluate basis function matrix: Nbf x nFOD
3693 arma::mat bf(bas.get_Nbf(), r.n_rows);
3694 for(size_t i=0;i<r.n_rows;i++)
3695 bf.col(i)=bas.eval_func(r(i,0),r(i,1),r(i,2));
3696 // Compute the values of the orbitals at the FODs
3697 arma::mat g(bf.t()*C); // nFOD x Nmo
3698 g.print("Orbitals' values at FODs");
3699 // Value of electron density at the FODs
3700 arma::vec n(arma::sqrt(arma::sum(arma::pow(g.t(),2))).t());
3701
3702 // T matrix (T_{ij}=g_j (r_i) / sqrt(n(r_i)/2))
3703 arma::mat T(r.n_rows, r.n_rows);
3704 for(size_t j=0;j<r.n_rows;j++)
3705 T.col(j)=g.col(j)/n;
3706
3707 arma::square(n).print("Electron density at FODs");
3708
3709 // Form unitary transform
3710 arma::mat TTt(T*T.t());
3711 arma::vec tval;
3712 arma::mat tvec;
3713 arma::eig_sym(tval,tvec,TTt);
3714
3715 arma::mat invsqrtTTt(tvec*arma::diagmat(arma::pow(tval,-0.5))*tvec.t());
3716 arma::mat U(invsqrtTTt*T);
3717
3718 // Orbitals are rotated as C -> C U^T
3719 U=U.t();
3720
3721 arma::mat grot(g*U);
3722 grot.print("FLO values at FODs");
3723
3724 return U;
3725 }
3726
compute_density(const arma::mat & P,const BasisSet & bas,const coords_t & r)3727 double compute_density(const arma::mat & P, const BasisSet & bas, const coords_t & r) {
3728 // Evaluate basis functions
3729 arma::vec bf=bas.eval_func(r.x,r.y,r.z);
3730
3731 // Density is
3732 return arma::as_scalar(arma::trans(bf)*P*bf);
3733 }
3734
compute_density_gradient(const arma::mat & P,const BasisSet & bas,const coords_t & r,double & d,arma::vec & g)3735 void compute_density_gradient(const arma::mat & P, const BasisSet & bas, const coords_t & r, double & d, arma::vec & g) {
3736 // Evaluate basis functions
3737 arma::vec bf=bas.eval_func(r.x,r.y,r.z);
3738 // and gradients
3739 arma::mat grad=bas.eval_grad(r.x,r.y,r.z);
3740
3741 // Density is
3742 d=arma::as_scalar(arma::trans(bf)*P*bf);
3743 // and the gradient
3744 g=arma::trans(arma::trans(bf)*P*grad);
3745 }
3746
compute_density_gradient_hessian(const arma::mat & P,const BasisSet & bas,const coords_t & r,double & d,arma::vec & g,arma::mat & h)3747 void compute_density_gradient_hessian(const arma::mat & P, const BasisSet & bas, const coords_t & r, double & d, arma::vec & g, arma::mat & h) {
3748 // Evaluate basis functions
3749 arma::vec bf=bas.eval_func(r.x,r.y,r.z);
3750 // and gradients
3751 arma::mat grad=bas.eval_grad(r.x,r.y,r.z);
3752 // and hessians
3753 arma::mat hess=bas.eval_hess(r.x,r.y,r.z);
3754
3755 // Density is
3756 d=arma::as_scalar(arma::trans(bf)*P*bf);
3757 // and the gradient
3758 g=arma::trans(arma::trans(bf)*P*grad);
3759
3760 // First part of hessian is
3761 arma::vec hf=arma::trans(bf)*P*hess;
3762 // and second part
3763 arma::mat hs=arma::trans(grad)*P*grad;
3764
3765 // Convert to matrix form
3766 h=2.0*(arma::reshape(hf,3,3)+hs);
3767 }
3768
compute_potential(const arma::mat & P,const BasisSet & bas,const coords_t & r)3769 double compute_potential(const arma::mat & P, const BasisSet & bas, const coords_t & r) {
3770 // Compute nuclear contribution
3771 std::vector<nucleus_t> nucs=bas.get_nuclei();
3772 double nucphi=0.0;
3773 for(size_t i=0;i<nucs.size();i++)
3774 if(!nucs[i].bsse)
3775 nucphi+=nucs[i].Z/norm(r - nucs[i].r);
3776
3777 // Get potential energy matrix
3778 arma::mat V=bas.potential(r);
3779 // Electronic contribution is (minus sign is already in the definition of the potential matrix)
3780 double elphi=arma::trace(P*V);
3781
3782 return nucphi+elphi;
3783 }
3784
compute_elf(const arma::mat & P,const BasisSet & bas,const coords_t & r)3785 double compute_elf(const arma::mat & P, const BasisSet & bas, const coords_t & r) {
3786 // Evaluate basis functions
3787 arma::vec bf=bas.eval_func(r.x,r.y,r.z);
3788 // and gradients
3789 arma::mat grad=bas.eval_grad(r.x,r.y,r.z);
3790
3791 // Compute kinetic energy term (eqn 9)
3792 double tau=arma::trace(arma::trans(grad)*P*grad);
3793
3794 // Compute rho and grad rho
3795 double rho=arma::as_scalar(arma::trans(bf)*P*bf);
3796 // and the gradient
3797 arma::vec grho=arma::trans(arma::trans(bf)*P*grad);
3798
3799 // The D value (eqn 10) is
3800 double D = tau - 0.25*arma::dot(grho,grho)/rho;
3801 // and the corresponding isotropic D is (eqn 13)
3802 double D0 = 3.0/5.0 * std::pow(6.0*M_PI*M_PI,2.0/3.0) * std::pow(rho,5.0/3.0);
3803
3804 // from which the chi value is
3805 double chi = D/D0;
3806
3807 // making the ELF
3808 return 1.0 / (1.0 + chi*chi);
3809 }
3810
find_identical_shells() const3811 std::vector< std::vector<size_t> > BasisSet::find_identical_shells() const {
3812 // Returned list of identical basis functions
3813 std::vector< std::vector<size_t> > ret;
3814
3815 // Loop over shells
3816 for(size_t ish=0;ish<shells.size();ish++) {
3817 // Get exponents, contractions and cartesian functions on shell
3818 std::vector<contr_t> shell_contr=shells[ish].get_contr();
3819 std::vector<shellf_t> shell_cart=shells[ish].get_cart();
3820
3821 // Try to find the shell on the current list of identicals
3822 bool found=false;
3823 for(size_t iident=0;iident<ret.size();iident++) {
3824
3825 // Check first cartesian part.
3826 std::vector<shellf_t> cmp_cart=shells[ret[iident][0]].get_cart();
3827
3828 if(shell_cart.size()==cmp_cart.size()) {
3829 // Default value
3830 found=true;
3831
3832 for(size_t icart=0;icart<shell_cart.size();icart++)
3833 if(shell_cart[icart].l!=cmp_cart[icart].l || shell_cart[icart].m!=cmp_cart[icart].m || shell_cart[icart].n!=cmp_cart[icart].n)
3834 found=false;
3835
3836 // Check that usage of spherical harmonics matches, too
3837 if(shells[ish].lm_in_use() != shells[ret[iident][0]].lm_in_use())
3838 found=false;
3839
3840 // If cartesian parts match, check also exponents and contraction coefficients
3841 if(found) {
3842 // Get exponents
3843 std::vector<contr_t> cmp_contr=shells[ret[iident][0]].get_contr();
3844
3845 // Check exponents
3846 if(shell_contr.size()==cmp_contr.size()) {
3847 for(size_t ic=0;ic<shell_contr.size();ic++)
3848 if(!(shell_contr[ic]==cmp_contr[ic]))
3849 found=false;
3850 } else
3851 found=false;
3852 }
3853
3854 // If everything matches, add the function to the current list.
3855 if(found) {
3856 ret[iident].push_back(ish);
3857 // Stop iteration over list of identical functions
3858 break;
3859 }
3860 }
3861 }
3862
3863 // If the shell was not found on the list of identicals, add it
3864 if(!found) {
3865 std::vector<size_t> hlp;
3866 hlp.push_back(ish);
3867 ret.push_back(hlp);
3868 }
3869 }
3870
3871 return ret;
3872 }
3873
orth_diff(const arma::mat & C,const arma::mat & S)3874 double orth_diff(const arma::mat & C, const arma::mat & S) {
3875 // Compute difference from unit overlap
3876 arma::mat d(arma::abs(arma::trans(C)*S*C-arma::eye<arma::mat>(C.n_cols,C.n_cols)));
3877 // Get maximum error
3878 return arma::max(arma::max(d));
3879 }
3880
orth_diff(const arma::cx_mat & C,const arma::mat & S)3881 double orth_diff(const arma::cx_mat & C, const arma::mat & S) {
3882 // Compute difference from unit overlap
3883 arma::mat d(arma::abs(arma::trans(C)*S*C - arma::eye<arma::mat>(C.n_cols,C.n_cols)));
3884 // Get maximum error
3885 return arma::max(arma::max(d));
3886 }
3887
check_orth(const arma::mat & C,const arma::mat & S,bool verbose,double thr)3888 void check_orth(const arma::mat & C, const arma::mat & S, bool verbose, double thr) {
3889 if(!C.n_cols)
3890 throw std::logic_error("Error in check_orth: no orbitals!\n");
3891 if(C.n_rows != S.n_rows) {
3892 std::ostringstream oss;
3893 oss << "Error in check_orth: got " << C.n_rows << " x " << C.n_cols << " C and " << S.n_rows << " x " << S.n_cols << " S!\n";
3894 throw std::logic_error(oss.str());
3895 }
3896
3897 // Compute difference from unit overlap
3898 arma::mat d(arma::abs(arma::trans(C)*S*C-arma::eye<arma::mat>(C.n_cols,C.n_cols)));
3899 // Get maximum error
3900 double maxerr(arma::max(arma::max(d)));
3901
3902 if(verbose) {
3903 printf("Maximum deviation from orthogonality is %e.\n",maxerr);
3904 fflush(stdout);
3905 }
3906
3907 if(maxerr>thr) {
3908 // Clean up
3909 for(size_t j=0;j<d.n_cols;j++)
3910 for(size_t i=0;i<d.n_cols;i++)
3911 if(fabs(d(i,j))<10*DBL_EPSILON)
3912 d(i,j)=0.0;
3913
3914 d.save("MOovl_diff.dat",arma::raw_ascii);
3915
3916 std::ostringstream oss;
3917 oss << "Generated orbitals are not orthonormal! Maximum deviation from orthonormality is " << maxerr <<".\nCheck the used LAPACK implementation.\n";
3918 throw std::runtime_error(oss.str());
3919 }
3920 }
3921
check_orth(const arma::cx_mat & C,const arma::mat & S,bool verbose,double thr)3922 void check_orth(const arma::cx_mat & C, const arma::mat & S, bool verbose, double thr) {
3923 if(!C.n_cols)
3924 throw std::logic_error("Error in check_orth: no orbitals!\n");
3925 if(C.n_rows != S.n_rows) {
3926 std::ostringstream oss;
3927 oss << "Error in check_orth: got " << C.n_rows << " x " << C.n_cols << " C and " << S.n_rows << " x " << S.n_cols << " S!\n";
3928 throw std::logic_error(oss.str());
3929 }
3930
3931 arma::mat d(arma::abs(arma::trans(C)*S*C - arma::eye<arma::cx_mat>(C.n_cols,C.n_cols)));
3932 double maxerr(arma::max(arma::max(d)));
3933
3934 if(verbose) {
3935 printf("Maximum deviation from orthogonality is %e.\n",maxerr);
3936 fflush(stdout);
3937 }
3938
3939 if(maxerr>thr) {
3940 // Clean up
3941 for(size_t i=0;i<d.n_cols;i++)
3942 for(size_t j=0;j<d.n_cols;j++)
3943 if(std::abs(d(i,j))<10*DBL_EPSILON)
3944 d(i,j)=0.0;
3945
3946 d.save("OMOovl_diff.dat",arma::raw_ascii);
3947
3948 std::ostringstream oss;
3949 oss << "Generated orbitals are not orthonormal! Maximum deviation from orthonormality is " << maxerr <<".\nCheck the used LAPACK implementation.\n";
3950 throw std::runtime_error(oss.str());
3951 }
3952 }
3953
construct_IAO_wrk(const BasisSet & basis,const arma::Mat<T> & C,std::vector<std::vector<size_t>> & idx,bool verbose,std::string minbaslib)3954 template<typename T> static arma::Mat<T> construct_IAO_wrk(const BasisSet & basis, const arma::Mat<T> & C, std::vector< std::vector<size_t> > & idx, bool verbose, std::string minbaslib) {
3955 // Get minao library
3956 BasisSetLibrary minao;
3957 minao.load_basis(minbaslib);
3958 // Default settings
3959 Settings set;
3960 set.add_scf_settings();
3961 set.set_bool("Verbose",verbose);
3962 // Can't rotate basis or it will break the contractions
3963 set.set_bool("BasisRotate",false);
3964
3965 // Construct minimal basis set
3966 BasisSet minbas;
3967 construct_basis(minbas,basis.get_nuclei(),minao);
3968
3969 // Get indices
3970 idx.clear();
3971 idx.resize(minbas.get_Nnuc());
3972 for(size_t inuc=0;inuc<minbas.get_Nnuc();inuc++) {
3973 // Get shells on nucleus
3974 std::vector<GaussianShell> sh=minbas.get_funcs(inuc);
3975 // Store indices
3976 for(size_t si=0;si<sh.size();si++)
3977 for(size_t fi=0;fi<sh[si].get_Nbf();fi++)
3978 idx[inuc].push_back(sh[si].get_first_ind()+fi);
3979 }
3980
3981 // Calculate S1, S12, S2, and S21
3982 arma::mat S1(basis.overlap());
3983 arma::mat S2(minbas.overlap());
3984
3985 arma::mat S12(basis.overlap(minbas));
3986 arma::mat S21(arma::trans(S12));
3987
3988 // and inverse matrices
3989 arma::mat S1inv_h(BasOrth(S1));
3990 arma::mat S2inv_h(BasOrth(S2));
3991 // Need to be OK for canonical as well
3992 arma::mat S1inv(S1inv_h*arma::trans(S1inv_h));
3993 arma::mat S2inv(S2inv_h*arma::trans(S2inv_h));
3994
3995 // Compute Ctilde.
3996 arma::Mat<T> Ctilde(S1inv*S12*S2inv*S21*C);
3997
3998 // and orthonormalize it
3999 Ctilde=orthonormalize(S1,Ctilde);
4000
4001 // "Density matrices"
4002 arma::Mat<T> P(C*arma::trans(C));
4003 arma::Mat<T> Pt(Ctilde*arma::trans(Ctilde));
4004
4005 // Identity matrix
4006 arma::mat unit(S1.n_rows,S1.n_cols);
4007 unit.eye();
4008
4009 // Compute the non-orthonormal IAOs.
4010 arma::Mat<T> A=P*S1*Pt*S12 + (unit-P*S1)*(unit-Pt*S1)*S1inv*S12;
4011
4012 // and orthonormalize them
4013 return orthonormalize(S1,A);
4014 }
construct_IAO(const BasisSet & basis,const arma::mat & C,std::vector<std::vector<size_t>> & idx,bool verbose,std::string minbaslib)4015 arma::mat construct_IAO(const BasisSet & basis, const arma::mat & C, std::vector< std::vector<size_t> > & idx, bool verbose, std::string minbaslib) {
4016 return construct_IAO_wrk<double>(basis,C,idx,verbose,minbaslib);
4017 }
construct_IAO(const BasisSet & basis,const arma::cx_mat & C,std::vector<std::vector<size_t>> & idx,bool verbose,std::string minbaslib)4018 arma::cx_mat construct_IAO(const BasisSet & basis, const arma::cx_mat & C, std::vector< std::vector<size_t> > & idx, bool verbose, std::string minbaslib) {
4019 return construct_IAO_wrk< std::complex<double> >(basis,C,idx,verbose,minbaslib);
4020 }
4021
block_m(const arma::mat & F,const arma::ivec & mv)4022 arma::mat block_m(const arma::mat & F, const arma::ivec & mv) {
4023 arma::mat Fnew(F);
4024 Fnew.zeros();
4025 for(arma::sword m=0;m<=mv.max();m++) {
4026 if(m==0) {
4027 // Indices are
4028 arma::uvec idx(arma::find(mv==m));
4029 Fnew(idx,idx)=F(idx,idx);
4030 } else {
4031 // Indices for plus and minus values are
4032 arma::uvec pidx(arma::find(mv==m));
4033 arma::uvec nidx(arma::find(mv==-m));
4034
4035 // m=m and m=-m are equivalent
4036 Fnew(pidx,pidx)=0.5*(F(pidx,pidx)+F(nidx,nidx));
4037 Fnew(nidx,nidx)=Fnew(pidx,pidx);
4038 }
4039 }
4040
4041 return Fnew;
4042 }
4043
m_norm(const arma::mat & C,const arma::ivec & mv)4044 arma::mat m_norm(const arma::mat & C, const arma::ivec & mv) {
4045 arma::mat osym(mv.max()-mv.min()+1,C.n_cols);
4046 for(arma::sword m=mv.min();m<=mv.max();m++) {
4047 arma::uvec idx(arma::find(mv==m));
4048 for(size_t io=0;io<C.n_cols;io++) {
4049 arma::vec cv(C.col(io));
4050 osym(m-mv.min(),io)=arma::norm(cv(idx),"fro");
4051 }
4052 }
4053
4054 return osym;
4055 }
4056
m_classify(const arma::mat & C,const arma::ivec & mv)4057 arma::ivec m_classify(const arma::mat & C, const arma::ivec & mv) {
4058 // Orbital class
4059 arma::ivec oclass(C.n_cols);
4060
4061 // Get symmetries
4062 arma::mat osym(m_norm(C,mv));
4063
4064 //osym.print("Orbital symmetry");
4065
4066 // Maximum angular momentum is
4067 if(osym.n_rows%2 != 1) throw std::logic_error("Invalid number of rows!\n");
4068 int maxam((osym.n_rows-1)/2);
4069
4070 for(size_t io=0;io<C.n_cols;io++) {
4071 arma::vec s(osym.col(io));
4072
4073 // Get maximum
4074 arma::uword idx;
4075 s.max(idx);
4076
4077 // This corresponds to the m value
4078 int m=idx;
4079 m-=maxam;
4080
4081 oclass(io)=m;
4082 }
4083
4084 return oclass;
4085 }
4086