Program Listing for File radial_quad.cpp

Return to documentation for file (/Users/robertshaw/devfiles/libecpint/src/lib/radial_quad.cpp)

/*
 *      Copyright (c) 2020 Robert Shaw
 *      This file is a part of Libecpint.
 *
 *      Permission is hereby granted, free of charge, to any person obtaining
 *      a copy of this software and associated documentation files (the
 *      "Software"), to deal in the Software without restriction, including
 *      without limitation the rights to use, copy, modify, merge, publish,
 *      distribute, sublicense, and/or sell copies of the Software, and to
 *      permit persons to whom the Software is furnished to do so, subject to
 *      the following conditions:
 *
 *      The above copyright notice and this permission notice shall be
 *      included in all copies or substantial portions of the Software.
 *
 *      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *      EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *      MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *      NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *      LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *      OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *      WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "radial.hpp"
#include "mathutil.hpp"
#include "Faddeeva.hpp"
#include <iostream>
#include <cmath>

namespace libecpint {

    RadialIntegral::RadialIntegral() {}

    void RadialIntegral::init(int maxL, double tol, int small, int large) {
        bigGrid.initGrid(large, ONEPOINT);
        primGrid.initGrid(128, ONEPOINT);
        smallGrid.initGrid(small, TWOPOINT);
        smallGrid.transformZeroInf();

        bessie.init(maxL, 1600, 200, tol);

        tolerance = tol;
    }

    void RadialIntegral::buildBessel(
        const std::vector<double> &r, const int nr, const int maxL, TwoIndex<double> &values, const double weight) const {
        std::vector<double> besselValues(maxL+1, 0.0);
        if (std::abs(weight) < 1e-15) {
            for (int i = 0; i < nr; i++) {
                values(0, i) = 1.0;
                for (int l = 1; l <= maxL; l++) values(l, i) = 0.0;
            }
        } else {
            for (int i = 0; i < nr; i++) {
                bessie.calculate(weight * r[i], maxL, besselValues);
                for (int l = 0; l <= maxL; l++) values(l, i) = besselValues[l];
            }
        }
    }

    double RadialIntegral::calcKij(
        const double Na, const double Nb, const double zeta_a, const double zeta_b, const double R2) const {
        double muij = zeta_a * zeta_b / (zeta_a + zeta_b);
        return Na * Nb * std::exp(-muij * R2);
    }

    // Assumes that p is the pretabulated integrand at the abscissae
    double RadialIntegral::integrand(const double r, const double *p, const int ix) {
        return p[ix];
    }

    RadialIntegral::Parameters RadialIntegral::buildParameters(
        const GaussianShell &shellA, const GaussianShell &shellB, const ShellPairData &data) const {
        int npA = shellA.nprimitive();
        int npB = shellB.nprimitive();

        // Initialise result object
        Parameters result;
        auto & p = result.p;
        auto & P = result.P;
        auto & P2 = result.P2;
        auto & K = result.K;

        p.assign(npA, npB, 0.0);
        P.assign(npA, npB, 0.0);
        P2.assign(npA, npB, 0.0);
        K.assign(npA, npB, 0.0);

        double Pvec[3];
        double zetaA, zetaB;
        for (int a = 0; a < npA; a++) {
            zetaA = shellA.exp(a);

            for (int b = 0; b < npB; b++) {
                zetaB = shellB.exp(b);

                p(a, b) = zetaA + zetaB;
                for (int n = 0; n < 3; n++)
                    Pvec[n] = (zetaA * data.A[n] + zetaB * data.B[n])/p(a, b);

                P2(a, b) = Pvec[0]*Pvec[0] + Pvec[1]*Pvec[1] + Pvec[2]*Pvec[2];
                P(a, b) = std::sqrt(P2(a, b));
                K(a, b) = calcKij(1.0, 1.0, zetaA, zetaB, data.RAB2);

            }
        }
        return result;
    }

    void RadialIntegral::buildU(
        const ECP &U, const int l, const int N, const GCQuadrature &grid, double *Utab) const {
        int gridSize = grid.getN();
    const std::vector<double> &gridPoints = grid.getX();

        // Tabulate weighted ECP values
        double r;
        for (int i = 0; i < gridSize; i++) {
            r = gridPoints[i];
            Utab[i] = FAST_POW[N+2](r) * U.evaluate(r, l);
        }
    }

    int RadialIntegral::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) const {
        std::function<double(double, const double*, int)> intgd = integrand;
        values.assign(maxL+1, 0.0);
        int test;
        double params[gridSize];
        for (int i = 0; i < start; i++) params[i] = 0.0;
        for (int i = end+1; i < gridSize; i++) params[i] = 0.0;
        for (int l = offset; l <= maxL; l+=skip) {
            for (int i = start; i <= end; i++) params[i] = intValues(l, i);
            const auto integral_and_test =
                grid.integrate(intgd, params, tolerance, start, end);
            values[l] = integral_and_test.first;
            test = integral_and_test.second;
            if (test == 0) break;
        }
        return test;
    }

    void RadialIntegral::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) const {
        int npA = shellA.nprimitive();
        int npB = shellB.nprimitive();

        int gridSize = bigGrid.getN();

        const auto & p = parameters.p;
        const auto & P = parameters.P;
        const auto & P2 = parameters.P2;
        const auto & K = parameters.K;

        // Now pretabulate integrand
        TwoIndex<double> intValues(maxL+1, gridSize, 0.0);
        // and bessel function
        TwoIndex<double> besselValues(maxL+1, gridSize, 0.0);
        // Calculate type1 integrals
        double da, db, za, zb, val;
        double A = data.Am;
        double B = data.Bm;
        std::vector<double> tempValues;
        values.assign(maxL+1, 2*maxL + 1, 0.0);

        // Tabulate integrand
        double x, phi, Px, Py;
        for (int a = 0; a < npA; a++) {
            da = shellA.coef(a);
            za = shellA.exp(a);

            for (int b = 0; b < npB; b++) {
                db = shellB.coef(b);
                zb = shellB.exp(b);

                // Reset grid starting points
                GCQuadrature newGrid = bigGrid;
                newGrid.transformRMinMax(p(a, b), (za * A + zb * B)/p(a, b));
                std::vector<double> &gridPoints = newGrid.getX();
                auto start = 0;
                auto end = gridSize - 1;

                // Build U and bessel tabs
                double Utab[gridSize];
                buildU(U, U.getL(), N, newGrid, Utab);
                buildBessel(gridPoints, gridSize, maxL, besselValues, 2.0*p(a,b)*P(a,b));

                // Start building intvalues, and prescreen
                bool foundStart = false, tooSmall = false;
                for (int i = 0; i < gridSize; i++) {
                    for (int l = offset; l <= maxL; l+=2) {
                        intValues(l, i) = Utab[i] * besselValues(l, i);
                        tooSmall = tooSmall || (intValues(l, i) < tolerance);
                    }
                    if (!tooSmall && !foundStart) {
                        foundStart = true;
                        start = i;
                    }
                    if (tooSmall && foundStart) {
                        end = i-1;
                        break;
                    }
                }

                for (int i = start; i <= end; i++) {
                    val = -p(a, b) * (gridPoints[i]*(gridPoints[i] - 2*P(a, b)) + P2(a, b));
                    val = std::exp(val);
                    for (int l = offset; l <= maxL; l+=2)
                        intValues(l, i) *= val;
                }

                int test = integrate(maxL, gridSize, intValues, newGrid, tempValues, start, end, offset, 2);
                if (test == 0)
                    std::cerr << "Failed to converge: " << U.atom_id << std::endl;

                // Calculate real spherical harmonic
                x = std::abs(P(a, b)) < 1e-12 ? 0.0 : (za * data.A[2] + zb * data.B[2]) / (p(a, b) * P(a, b));
                Py = (za * data.A[1] + zb * data.B[1]) / p(a, b);
                Px = (za * data.A[0] + zb * data.B[0]) / p(a, b);
                phi = std::atan2(Py, Px);

                TwoIndex<double> harmonics = realSphericalHarmonics(maxL, x, phi);
                for (int l = offset; l <= maxL; l+=2) {
                    for (int mu = -l; mu <= l; mu++)
                        values(l, l+mu) += da * db * harmonics(l, l+mu) * K(a, b) * tempValues[l];
                }
            }
        }
        //std::cout << "\n\n";
    }

    // F_a(lam, r) = sum_{i in a} d_i K_{lam}(2 zeta_a A r)*std::exp(-zeta_a(r - A)^2)
    void RadialIntegral::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) const {
        int np = shell.nprimitive();

        double weight, zeta, c;
        TwoIndex<double> besselValues(lend+1, nr, 0.0);

        F.assign(lend + 1, nr, 0.0);
        for (int a = 0; a < np; a++) {
            zeta = shell.exp(a);
            c = shell.coef(a);
            weight = 2.0 * zeta * A;

            buildBessel(r, nr, lend, besselValues, weight);

            for (int i = start; i <= end; i++) {
                weight = r[i] - A;
                weight = c * std::exp(-zeta * weight * weight);

                for (int l = lstart; l <= lend; l++)
                    F(l, i) += weight * besselValues(l, i);
            }
        }
    }

    double RadialIntegral::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) const {
        double kA = 2.0*a*A;
        double kB = 2.0*b*B;
        double c0 = std::max(N - l1 - l2, 0);
        double c1_min = kA + kB;
        double p = a + b + n;

        double P = c1_min + std::sqrt(c1_min*c1_min + 8.0*p*c0);
        P /= (4.0*p);

        double zA = P - A;
        double zB = P - B;
        double besselValue1 = bessie.upper_bound(kA * P, l1);
        double besselValue2 = bessie.upper_bound(kB * P, l2);
        double Fres = FAST_POW[N](P) * std::exp(-n * P * P - a * zA * zA - b * zB * zB) * besselValue1 * besselValue2;
        return (0.5 * std::sqrt(M_PI/p) * Fres * (1.0 + Faddeeva::erf(std::sqrt(p)*P)));
    }

    void RadialIntegral::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) const {

        std::function<double(double, const double*, int)> intgd = integrand;

        int npA = shellA.nprimitive();
        int npB = shellB.nprimitive();

        double A = data.Am;
        double B = data.Bm;

        const auto & p = parameters.p;
        const auto & P = parameters.P;
        const auto & P2 = parameters.P2;
        const auto & K = parameters.K;

        // Start with the small grid
        // Pretabulate U
        int gridSize = smallGrid.getN();
        const std::vector<double> &gridPoints = smallGrid.getX();

        // Reset grid starting points
        const auto start = 0;
        const auto end = gridSize-1;

        double Utab[gridSize];
        buildU(U, l, N, smallGrid, Utab);
        values.assign(l1end+1, l2end+1, 0.0);

        // Build the F matrices
        if (A < 1e-15) l1end = 0;
        if (B < 1e-15) l2end = 0;
        TwoIndex<double> Fa;
        TwoIndex<double> Fb;
        buildF(shellA, data.Am, l1start, l1end, gridPoints, gridSize, start, end, Fa);
        buildF(shellB, data.Bm, l2start, l2end, gridPoints, gridSize, start, end, Fb);

        // Build the integrals
        bool foundStart, tooSmall;
        std::vector<int> tests((l1end +1) * (l2end+1));
        double params[gridSize];
        bool failed = false;
        int ix = 0;
        for (int l1 = 0; l1 <= l1end; l1++) {
            int l2start = (l1 + N) % 2;
            for (int l2 = l2start; l2 <= l2end; l2+=2) {

                for (int i = 0; i < gridSize; i++) params[i] = Utab[i] * Fa(l1, i) * Fb(l2, i);
                const auto this_integral_and_test = smallGrid.integrate(intgd, params, tolerance, start, end);
                tests[ix] = this_integral_and_test.second;
                failed = failed || (tests[ix] == 0);
                values(l1, l2) = tests[ix] == 0 ? 0.0 : this_integral_and_test.first;
                ix++;
            }
        }

        if (failed) {
            // Not converged, switch to big grid
            double zeta_a, zeta_b, c_a, c_b;

            gridSize = bigGrid.getN();
            Fa.assign(l1end+1, gridSize, 0.0);
            Fb.assign(l2end+1, gridSize, 0.0);

            for (int a = 0; a < npA; a++) {
                c_a = shellA.coef(a);
                zeta_a = shellA.exp(a);

                for (int b = 0; b < npB; b++) {
                    c_b = shellB.coef(b);
                    zeta_b = shellB.exp(b);

                    GCQuadrature newGrid = bigGrid;
                    newGrid.transformRMinMax(p(a, b), (zeta_a * A + zeta_b * B)/p(a, b));
                    std::vector<double> &gridPoints2 = newGrid.getX();
                    const auto start = 0;
                    const auto end = gridSize - 1;

                    // Build U and bessel tabs
                    double Utab2[gridSize];
                    buildU(U, l, N, newGrid, Utab2);
                    buildBessel(gridPoints2, gridSize, l1end, Fa, 2.0*zeta_a*A);
                    buildBessel(gridPoints2, gridSize, l2end, Fb, 2.0*zeta_b*B);

                    double Xvals[gridSize];
                    double ria, rib;
                    for (int i = 0; i < gridSize; i++) {
                        ria = gridPoints2[i] - A;
                        rib = gridPoints2[i] - B;
                        Xvals[i] = std::exp(-zeta_a*ria*ria -zeta_b*rib*rib) * Utab2[i];
                    }

                    double params2[gridSize];
                    ix = 0;
                    for (int l1 = 0; l1 <= l1end; l1++) {
                        int l2start = (l1 + N) % 2;

                        for (int l2 = l2start; l2 <= l2end; l2+=2) {

                            if (tests[ix] == 0) {
                                for (int i = 0; i < gridSize; i++)
                                    params2[i] = Xvals[i] * Fa(l1, i) * Fb(l2, i);
                                const auto integral_and_test =
                                    newGrid.integrate(intgd, params2, tolerance, start, end);
                                if (!integral_and_test.second) std::cerr << "Failed at second attempt" << std::endl;
                                values(l1, l2) += c_a * c_b * integral_and_test.first;
                            }
                            ix++;

                        }
                    }

                }
            }

        }
    }

}