1 /* 2 * Copyright (c) 2020 Robert Shaw 3 * This file is a part of Libecpint. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining 6 * a copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sublicense, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be 14 * included in all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 */ 24 25 #include "radial.hpp" 26 #include "mathutil.hpp" 27 #include <iostream> 28 #include <cmath> 29 30 namespace libecpint { 31 RadialIntegral()32 RadialIntegral::RadialIntegral() {} 33 init(int maxL,double tol,int small,int large)34 void RadialIntegral::init(int maxL, double tol, int small, int large) { 35 bigGrid.initGrid(large, ONEPOINT); 36 primGrid.initGrid(128, ONEPOINT); 37 smallGrid.initGrid(small, TWOPOINT); 38 smallGrid.transformZeroInf(); 39 40 bessie.init(maxL, 1600, 200, tol); 41 42 tolerance = tol; 43 } 44 buildBessel(const std::vector<double> & r,const int nr,const int maxL,TwoIndex<double> & values,const double weight) const45 void RadialIntegral::buildBessel( 46 const std::vector<double> &r, const int nr, const int maxL, TwoIndex<double> &values, const double weight) const { 47 std::vector<double> besselValues(maxL+1, 0.0); 48 if (std::abs(weight) < 1e-15) { 49 for (int i = 0; i < nr; i++) { 50 values(0, i) = 1.0; 51 for (int l = 1; l <= maxL; l++) values(l, i) = 0.0; 52 } 53 } else { 54 for (int i = 0; i < nr; i++) { 55 bessie.calculate(weight * r[i], maxL, besselValues); 56 for (int l = 0; l <= maxL; l++) values(l, i) = besselValues[l]; 57 } 58 } 59 } 60 calcKij(const double Na,const double Nb,const double zeta_a,const double zeta_b,const double R2) const61 double RadialIntegral::calcKij( 62 const double Na, const double Nb, const double zeta_a, const double zeta_b, const double R2) const { 63 double muij = zeta_a * zeta_b / (zeta_a + zeta_b); 64 return Na * Nb * std::exp(-muij * R2); 65 } 66 67 // Assumes that p is the pretabulated integrand at the abscissae integrand(const double r,const double * p,const int ix)68 double RadialIntegral::integrand(const double r, const double *p, const int ix) { 69 return p[ix]; 70 } 71 buildParameters(const GaussianShell & shellA,const GaussianShell & shellB,const ShellPairData & data) const72 RadialIntegral::Parameters RadialIntegral::buildParameters( 73 const GaussianShell &shellA, const GaussianShell &shellB, const ShellPairData &data) const { 74 int npA = shellA.nprimitive(); 75 int npB = shellB.nprimitive(); 76 77 // Initialise result object 78 Parameters result; 79 auto & p = result.p; 80 auto & P = result.P; 81 auto & P2 = result.P2; 82 auto & K = result.K; 83 84 p.assign(npA, npB, 0.0); 85 P.assign(npA, npB, 0.0); 86 P2.assign(npA, npB, 0.0); 87 K.assign(npA, npB, 0.0); 88 89 double Pvec[3]; 90 double zetaA, zetaB; 91 for (int a = 0; a < npA; a++) { 92 zetaA = shellA.exp(a); 93 94 for (int b = 0; b < npB; b++) { 95 zetaB = shellB.exp(b); 96 97 p(a, b) = zetaA + zetaB; 98 for (int n = 0; n < 3; n++) 99 Pvec[n] = (zetaA * data.A[n] + zetaB * data.B[n])/p(a, b); 100 101 P2(a, b) = Pvec[0]*Pvec[0] + Pvec[1]*Pvec[1] + Pvec[2]*Pvec[2]; 102 P(a, b) = std::sqrt(P2(a, b)); 103 K(a, b) = calcKij(1.0, 1.0, zetaA, zetaB, data.RAB2); 104 105 } 106 } 107 return result; 108 } 109 buildU(const ECP & U,const int l,const int N,const GCQuadrature & grid,double * Utab) const110 void RadialIntegral::buildU( 111 const ECP &U, const int l, const int N, const GCQuadrature &grid, double *Utab) const { 112 int gridSize = grid.getN(); 113 const std::vector<double> &gridPoints = grid.getX(); 114 115 // Tabulate weighted ECP values 116 double r; 117 for (int i = 0; i < gridSize; i++) { 118 r = gridPoints[i]; 119 Utab[i] = FAST_POW[N+2](r) * U.evaluate(r, l); 120 } 121 } 122 integrate(const int maxL,const int gridSize,const TwoIndex<double> & intValues,GCQuadrature & grid,std::vector<double> & values,const int start,const int end,const int offset,const int skip) const123 int RadialIntegral::integrate( 124 const int maxL, const int gridSize, const TwoIndex<double> &intValues, GCQuadrature &grid, 125 std::vector<double> &values, const int start, const int end, const int offset, const int skip) const { 126 std::function<double(double, const double*, int)> intgd = integrand; 127 values.assign(maxL+1, 0.0); 128 int test; 129 double params[gridSize]; 130 for (int i = 0; i < start; i++) params[i] = 0.0; 131 for (int i = end+1; i < gridSize; i++) params[i] = 0.0; 132 for (int l = offset; l <= maxL; l+=skip) { 133 for (int i = start; i <= end; i++) params[i] = intValues(l, i); 134 const auto integral_and_test = 135 grid.integrate(intgd, params, tolerance, start, end); 136 values[l] = integral_and_test.first; 137 test = integral_and_test.second; 138 if (test == 0) break; 139 } 140 return test; 141 } 142 type1(const int maxL,const int N,const int offset,const ECP & U,const GaussianShell & shellA,const GaussianShell & shellB,const ShellPairData & data,const Parameters & parameters,TwoIndex<double> & values) const143 void RadialIntegral::type1( 144 const int maxL, const int N, const int offset, 145 const ECP &U, const GaussianShell &shellA, const GaussianShell &shellB, 146 const ShellPairData &data, const Parameters & parameters, TwoIndex<double> &values) const { 147 int npA = shellA.nprimitive(); 148 int npB = shellB.nprimitive(); 149 150 int gridSize = bigGrid.getN(); 151 152 const auto & p = parameters.p; 153 const auto & P = parameters.P; 154 const auto & P2 = parameters.P2; 155 const auto & K = parameters.K; 156 157 // Now pretabulate integrand 158 TwoIndex<double> intValues(maxL+1, gridSize, 0.0); 159 // and bessel function 160 TwoIndex<double> besselValues(maxL+1, gridSize, 0.0); 161 // Calculate type1 integrals 162 double da, db, za, zb, val; 163 double A = data.Am; 164 double B = data.Bm; 165 std::vector<double> tempValues; 166 values.assign(maxL+1, 2*maxL + 1, 0.0); 167 168 // Tabulate integrand 169 double x, phi, Px, Py; 170 for (int a = 0; a < npA; a++) { 171 da = shellA.coef(a); 172 za = shellA.exp(a); 173 174 for (int b = 0; b < npB; b++) { 175 db = shellB.coef(b); 176 zb = shellB.exp(b); 177 178 // Reset grid starting points 179 GCQuadrature newGrid = bigGrid; 180 newGrid.transformRMinMax(p(a, b), (za * A + zb * B)/p(a, b)); 181 std::vector<double> &gridPoints = newGrid.getX(); 182 auto start = 0; 183 auto end = gridSize - 1; 184 185 // Build U and bessel tabs 186 double Utab[gridSize]; 187 buildU(U, U.getL(), N, newGrid, Utab); 188 buildBessel(gridPoints, gridSize, maxL, besselValues, 2.0*p(a,b)*P(a,b)); 189 190 // Start building intvalues, and prescreen 191 bool foundStart = false, tooSmall = false; 192 for (int i = 0; i < gridSize; i++) { 193 for (int l = offset; l <= maxL; l+=2) { 194 intValues(l, i) = Utab[i] * besselValues(l, i); 195 tooSmall = tooSmall || (intValues(l, i) < tolerance); 196 } 197 if (!tooSmall && !foundStart) { 198 foundStart = true; 199 start = i; 200 } 201 if (tooSmall && foundStart) { 202 end = i-1; 203 break; 204 } 205 } 206 207 for (int i = start; i <= end; i++) { 208 val = -p(a, b) * (gridPoints[i]*(gridPoints[i] - 2*P(a, b)) + P2(a, b)); 209 val = std::exp(val); 210 for (int l = offset; l <= maxL; l+=2) 211 intValues(l, i) *= val; 212 } 213 214 int test = integrate(maxL, gridSize, intValues, newGrid, tempValues, start, end, offset, 2); 215 if (test == 0) std::cerr << "Failed to converge" << std::endl; 216 217 // Calculate real spherical harmonic 218 x = std::abs(P(a, b)) < 1e-12 ? 0.0 : (za * data.A[2] + zb * data.B[2]) / (p(a, b) * P(a, b)); 219 Py = (za * data.A[1] + zb * data.B[1]) / p(a, b); 220 Px = (za * data.A[0] + zb * data.B[0]) / p(a, b); 221 phi = std::atan2(Py, Px); 222 223 TwoIndex<double> harmonics = realSphericalHarmonics(maxL, x, phi); 224 for (int l = offset; l <= maxL; l+=2) { 225 for (int mu = -l; mu <= l; mu++) 226 values(l, l+mu) += da * db * harmonics(l, l+mu) * K(a, b) * tempValues[l]; 227 } 228 } 229 } 230 //std::cout << "\n\n"; 231 } 232 233 // F_a(lam, r) = sum_{i in a} d_i K_{lam}(2 zeta_a A r)*std::exp(-zeta_a(r - A)^2) buildF(const GaussianShell & shell,const double A,const int lstart,const int lend,const std::vector<double> & r,const int nr,const int start,const int end,TwoIndex<double> & F) const234 void RadialIntegral::buildF( 235 const GaussianShell &shell, const double A, const int lstart, const int lend, 236 const std::vector<double> &r, const int nr, const int start, const int end, 237 TwoIndex<double> &F) const { 238 int np = shell.nprimitive(); 239 240 double weight, zeta, c; 241 TwoIndex<double> besselValues(lend+1, nr, 0.0); 242 243 F.assign(lend + 1, nr, 0.0); 244 for (int a = 0; a < np; a++) { 245 zeta = shell.exp(a); 246 c = shell.coef(a); 247 weight = 2.0 * zeta * A; 248 249 buildBessel(r, nr, lend, besselValues, weight); 250 251 for (int i = start; i <= end; i++) { 252 weight = r[i] - A; 253 weight = c * std::exp(-zeta * weight * weight); 254 255 for (int l = lstart; l <= lend; l++) 256 F(l, i) += weight * besselValues(l, i); 257 } 258 } 259 } 260 estimate_type2(const int N,const int l1,const int l2,const double n,const double a,const double b,const double A,const double B) const261 double RadialIntegral::estimate_type2( 262 const int N, const int l1, const int l2, const double n, 263 const double a, const double b, const double A, const double B) const { 264 double kA = 2.0*a*A; 265 double kB = 2.0*b*B; 266 double c0 = std::max(N - l1 - l2, 0); 267 double c1_min = kA + kB; 268 double p = a + b + n; 269 270 double P = c1_min + std::sqrt(c1_min*c1_min + 8.0*p*c0); 271 P /= (4.0*p); 272 273 double zA = P - A; 274 double zB = P - B; 275 double besselValue1 = bessie.upper_bound(kA * P, l1); 276 double besselValue2 = bessie.upper_bound(kB * P, l2); 277 double Fres = FAST_POW[N](P) * std::exp(-n * P * P - a * zA * zA - b * zB * zB) * besselValue1 * besselValue2; 278 return (0.5 * std::sqrt(M_PI/p) * Fres * (1.0 + std::erf(std::sqrt(p)*P))); 279 } 280 type2(const int l,const int l1start,int l1end,const int l2start,int l2end,const int N,const ECP & U,const GaussianShell & shellA,const GaussianShell & shellB,const ShellPairData & data,const Parameters & parameters,TwoIndex<double> & values) const281 void RadialIntegral::type2( 282 const int l, const int l1start, int l1end, const int l2start, int l2end, 283 const int N, const ECP &U, const GaussianShell &shellA, const GaussianShell &shellB, 284 const ShellPairData &data, const Parameters & parameters, TwoIndex<double> &values) const { 285 286 std::function<double(double, const double*, int)> intgd = integrand; 287 288 int npA = shellA.nprimitive(); 289 int npB = shellB.nprimitive(); 290 291 double A = data.Am; 292 double B = data.Bm; 293 294 const auto & p = parameters.p; 295 const auto & P = parameters.P; 296 const auto & P2 = parameters.P2; 297 const auto & K = parameters.K; 298 299 // Start with the small grid 300 // Pretabulate U 301 int gridSize = smallGrid.getN(); 302 const std::vector<double> &gridPoints = smallGrid.getX(); 303 304 // Reset grid starting points 305 const auto start = 0; 306 const auto end = gridSize-1; 307 308 double Utab[gridSize]; 309 buildU(U, l, N, smallGrid, Utab); 310 values.assign(l1end+1, l2end+1, 0.0); 311 312 // Build the F matrices 313 if (A < 1e-15) l1end = 0; 314 if (B < 1e-15) l2end = 0; 315 TwoIndex<double> Fa; 316 TwoIndex<double> Fb; 317 buildF(shellA, data.Am, l1start, l1end, gridPoints, gridSize, start, end, Fa); 318 buildF(shellB, data.Bm, l2start, l2end, gridPoints, gridSize, start, end, Fb); 319 320 // Build the integrals 321 bool foundStart, tooSmall; 322 std::vector<int> tests((l1end +1) * (l2end+1)); 323 double params[gridSize]; 324 bool failed = false; 325 int ix = 0; 326 for (int l1 = 0; l1 <= l1end; l1++) { 327 int l2start = (l1 + N) % 2; 328 for (int l2 = l2start; l2 <= l2end; l2+=2) { 329 330 for (int i = 0; i < gridSize; i++) params[i] = Utab[i] * Fa(l1, i) * Fb(l2, i); 331 const auto this_integral_and_test = smallGrid.integrate(intgd, params, tolerance, start, end); 332 tests[ix] = this_integral_and_test.second; 333 failed = failed || (tests[ix] == 0); 334 values(l1, l2) = tests[ix] == 0 ? 0.0 : this_integral_and_test.first; 335 ix++; 336 } 337 } 338 339 if (failed) { 340 // Not converged, switch to big grid 341 double zeta_a, zeta_b, c_a, c_b; 342 343 gridSize = bigGrid.getN(); 344 Fa.assign(l1end+1, gridSize, 0.0); 345 Fb.assign(l2end+1, gridSize, 0.0); 346 347 for (int a = 0; a < npA; a++) { 348 c_a = shellA.coef(a); 349 zeta_a = shellA.exp(a); 350 351 for (int b = 0; b < npB; b++) { 352 c_b = shellB.coef(b); 353 zeta_b = shellB.exp(b); 354 355 GCQuadrature newGrid = bigGrid; 356 newGrid.transformRMinMax(p(a, b), (zeta_a * A + zeta_b * B)/p(a, b)); 357 std::vector<double> &gridPoints2 = newGrid.getX(); 358 const auto start = 0; 359 const auto end = gridSize - 1; 360 361 // Build U and bessel tabs 362 double Utab2[gridSize]; 363 buildU(U, l, N, newGrid, Utab2); 364 buildBessel(gridPoints2, gridSize, l1end, Fa, 2.0*zeta_a*A); 365 buildBessel(gridPoints2, gridSize, l2end, Fb, 2.0*zeta_b*B); 366 367 double Xvals[gridSize]; 368 double ria, rib; 369 for (int i = 0; i < gridSize; i++) { 370 ria = gridPoints2[i] - A; 371 rib = gridPoints2[i] - B; 372 Xvals[i] = std::exp(-zeta_a*ria*ria -zeta_b*rib*rib) * Utab2[i]; 373 } 374 375 double params2[gridSize]; 376 ix = 0; 377 for (int l1 = 0; l1 <= l1end; l1++) { 378 int l2start = (l1 + N) % 2; 379 380 for (int l2 = l2start; l2 <= l2end; l2+=2) { 381 382 if (tests[ix] == 0) { 383 for (int i = 0; i < gridSize; i++) 384 params2[i] = Xvals[i] * Fa(l1, i) * Fb(l2, i); 385 const auto integral_and_test = 386 newGrid.integrate(intgd, params2, tolerance, start, end); 387 if (!integral_and_test.second) std::cerr << "Failed at second attempt" << std::endl; 388 values(l1, l2) += c_a * c_b * integral_and_test.first; 389 } 390 ix++; 391 392 } 393 } 394 395 } 396 } 397 398 } 399 } 400 401 } 402