Program Listing for File bessel.cpp

Return to documentation for file (/Users/robertshaw/devfiles/libecpint/src/lib/bessel.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 "bessel.hpp"
#include "mathutil.hpp"
#include <cmath>
#include <cassert>
#include <iostream>

namespace libecpint {

    // Constructor
    BesselFunction::BesselFunction() {}
    BesselFunction::BesselFunction(const int _lMax, const int _N, const int _order, const double accuracy)
    {
        init(_lMax, _N, _order, accuracy);
    }

    void BesselFunction::init(const int _lMax, const int _N, const int _order, const double accuracy) {
        // Check parameters
        lMax = _lMax > -1 ? _lMax : 0;
        N = _N > 0 ? _N : 1;
        order = _order > 0 ? _order : 1;
        scale = N/16.0;

        // Allocate arrays
        K = new double*[N+1];
        dK = new double**[N+1];
        for (int i = 0; i < N+1; i++) {
            K[i] = new double[lMax + TAYLOR_CUT + 1];
            dK[i] = new double*[TAYLOR_CUT + 1];
            for (int j = 0; j < TAYLOR_CUT + 1; j++)
                dK[i][j] = new double[lMax + TAYLOR_CUT + 1];
        }
        C = new double[lMax+TAYLOR_CUT];

        // Tabulate values
        tabulate(accuracy);
    }

    BesselFunction::~BesselFunction() {
        free(K);
        free(dK);
        free(C);
    }

    // Tabulate the bessel function values
    int BesselFunction::tabulate(const double accuracy) {
        int retval = 0; // 0 for success, -1 for not converged
        // Series expansion for bessel function, K, is given by:
        // K_l(z) ~ z^l sum_{j=0 to infty} F_j(z) / (2j + 2l + 1)!!
        // where F_j(z) = e^(-z) * (z^2/2)^j / j!
        int lmax = lMax + TAYLOR_CUT;

        double F[order + 1]; // F_j above

        K[0][0] = 1.0;
        double z, z2; // z and z^2 / 2
        double ratio; // F_j(z) / (2j+1)!!
        for (int i = 0; i <= N; i++) {
            // Calculate K(z) at equally spaced points z = 16/N to 16
            z = i / (N/16.0);
            z2 = z * z / 2.0;

            F[0] = exp(-z);
            ratio = F[0] / DFAC[0];
            K[i][0] = ratio;

            // Series expansion for K_0(z)
            int l = order;
            int j;
            for (j = 1; j <= l; j++) {

                if (ratio < accuracy) {
                    // Reached convergence
                    break;
                }

                F[j] = F[j-1] * z2 / ((double)j);
                ratio = F[j] / DFAC[2*j+1];
                K[i][0] += ratio;
            }
            //if ( ratio > accuracy ) { retval = -1; break; } // Not converged

            // Calculate K_l from K_0
            z2 = z;
            for (l=1; l<=lmax; l++) {
                ratio = 0;
                for (int m=0; m < j; m++) ratio += F[m]/DFAC[2*l + 2*m + 1];
                K[i][l] = z2 * ratio;
                z2 *= z;
            }

        }

        // Determine coefficients for derivative recurrence
        for (int i = 1; i<lmax; i++) C[i] = i/(2.0*i + 1.0);

        // Determine the necessary derivatives from
        // K_l^(n+1) = C_l K_(l-1)^(n) + (C_l + 1/(2l+1))K_(l+1)^(n) - K_l^(n)
        for (int ix = 0; ix < N+1; ix++) {
            // Copy K values into dK
            for (int l = 0; l <= lMax+TAYLOR_CUT; l++)
                dK[ix][0][l] = K[ix][l];

            // Then the rest
            for (int n = 1; n < TAYLOR_CUT+1; n++) {
                dK[ix][n][0] = dK[ix][n-1][1] - dK[ix][n-1][0];
                for (int l = 1; l <= lMax + TAYLOR_CUT - n; l++)
                    dK[ix][n][l] = C[l]*dK[ix][n-1][l-1] + (C[l] + 1.0/(2.0*l + 1.0))*dK[ix][n-1][l+1] - dK[ix][n-1][l];
            }
        }

        return retval;
    }

    // Get an upper bound for M_l(z)
    double BesselFunction::upper_bound(const double z, const int L) const {
        // find nearest point (on left) in tabulated values
        int ix = std::floor(N*z/16.0);
        int minix = L > 0 ? 1 : 0;
        ix = std::min(N, std::max(minix, ix));
        int lx = std::min(L, lMax);
        return K[ix][lx];
    }

    // Calculate modified spherical Bessel function K_l(z), weighted with an exponential factor e^(-z)
    // for l = 0 to lMax. This restricts K(z) to the interval [0,1].
    void BesselFunction::calculate(const double z, int maxL, std::vector<double> &values) const {
        if (lMax < maxL) {
            std::cout << "Asked for " << maxL << " but only initialised to maximum L = " << lMax << "\n";
            maxL = lMax;
        }

        // Set K_0(z) = 1.0, and K_l(z) = 0.0 (for l != 0) if z <= 0
        if (z <= 0) values[0] = 1.0;
        // Zeroth order case
        // K_l(z) ~ (1-z)*z^l / (2l + 1)!!
        else if (z < SMALL) {
            values[0] = 1.0 - z;
            for (int l = 1; l <= maxL; l++) values[l] = values[l-1]*z/(2.0*l+1.0);
        }
        // Large z case
        // K_l(z) ~ R_l(-z)/(2z)
        // where R_l(z) = sum_{k=0 to l} T_l,k(z)
        // where T_l,k(z) = (l+k)!/[k!(l-k)!] * (2z)^{-k}
        else if (z > 16.0) {
            values[0] = 0.5/z;
            for (int l = 1; l <= maxL; l++) {
                values[l] = values[0];
                double Rl = 1.0;
                double Tlk = 1.0;
                double cof = 1.0;
                for (int k = 1; k <= l; k++) {
                    cof = (l-k+1)*(l+k)/((double)k);
                    Tlk *= - cof * values[0];
                    Rl += Tlk;
                }
                values[l] *= Rl;
            }
        }
        // SMALL < z < 16
        // Use Taylor series around pretabulated values in class
        // 5 terms is usually sufficient for machine accuracy
        else {
            // Index of abscissa z in table
            int ix = std::floor(z * scale + 0.5);
            double dz = z - ix/scale; // z - z0

            if (fabs(dz) < 1e-12) { // z is one of the tabulated points
                for (int l = 0; l <= maxL; l++) values[l] = K[ix][l];
            } else {

                // Calculate (dz)^n/n! terms just once
                double dzn[TAYLOR_CUT+1];
                dzn[0] = 1.0;
                for (int n = 1; n < TAYLOR_CUT + 1; n++)
                    dzn[n] = dzn[n-1] * dz / ((double) n);

                // Now tabulate the values through Taylor seris
                // K(z) ~ sum_{n=0 to 5} K^(n)(z0)(z-z0)^n / n!
                for (int l = 0; l <= maxL; l++) {
                    values[l] = 0.0;
                    for (int n = 0; n < TAYLOR_CUT+1; n++)
                        values[l] += dzn[n] * dK[ix][n][l];
                }
            }
        }
    }

    // Calculate a modified spherical bessel function value at a point for only a single L
    // method the same as in calculate for multiple L, but with efficiencies
    double BesselFunction::calculate(const double z, const int L) const {
        double value = 0.0;

        if (z <= 0) value = 1.0;
        else if (z < SMALL) {
            value = 1.0 - z;
            for (int k = 1; k < L+1; k++)
                value *= z/(2.0*L+1.0);
        } else if (z > 16.0) {
            double v0 = 0.5/z;
            value = 1.0;
            double Tlk = 1.0;
            for (int k = 1; k < L+1; k++) {
                Tlk *= -v0 * (L - k +1)*(L+k)/(double(k));
                value += Tlk;
            }
            value = v0 * value;
        } else {
            int ix = std::floor(z * scale + 0.5);
            double dz = z - ix/scale; // z - z0
            double dzn = 1.0;
            for (int n = 0; n < TAYLOR_CUT+1; n++) {
                value += dzn * dK[ix][n][L];
                dzn *= dz / (n+1);
            }
        }

        return value;
    }
}