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