fmm_m2p_work Subroutine

private subroutine fmm_m2p_work(c, src_r, p, vscales_rel, alpha, src_m, beta, dst_v, work)

Accumulate potential, induced by multipole spherical harmonics

This function relies on a user-provided temporary workspace

Computes the following sum: \f[ v = \beta v + \alpha \sum_{\ell=0}^p \frac{4\pi}{\sqrt{2\ell+1}} \left( \frac{r}{|c|} \right)^{\ell+1} \sum_{m=-\ell}^\ell M_\ell^m Y_\ell^m \left( \frac{c}{|c|} \right), \f] where \f$ M \f$ is a vector of coefficients of input harmonics of a degree up to \f$ p \f$ inclusively with a convergence radius \f$ r \f$ located at the origin, \f$ \alpha \f$ and \f$ \beta \f$ are scaling factors and \f$ c \f$ is a location of a particle.

Based on normalized real spherical harmonics \f$ Y_\ell^m \f$, scaled by \f$ r^{-\ell} \f$. It means corresponding coefficients are simply scaled by an additional factor \f$ r^\ell \f$.

@param[in] c: Coordinates of a particle (relative to center of harmonics) @param[in] r: Radius of spherical harmonics @param[in] p: Maximal degree of multipole basis functions @param[in] vscales_rel: Relative normalization constants for \f$ Y_\ell^m \f$. Dimension is (p+1)**2 @param[in] alpha: Scalar multiplier for src_m @param[in] src_m: Multipole coefficients. Dimension is (p+1)**2 @param[in] beta: Scalar multiplier for v @param[inout] v: Value of induced potential @param[out] work: Temporary workspace of size (p+1)

Arguments

Type IntentOptional Attributes Name
real(kind=rp), intent(in) :: c(3)
real(kind=rp), intent(in) :: src_r
integer, intent(in) :: p
real(kind=rp), intent(in) :: vscales_rel((p+1)*(p+1))
real(kind=rp), intent(in) :: alpha
real(kind=rp), intent(in) :: src_m((p+1)*(p+1))
real(kind=rp), intent(in) :: beta
real(kind=rp), intent(inout) :: dst_v
real(kind=rp), intent(out), target :: work(p+1)

Called by

proc~~fmm_m2p_work~~CalledByGraph proc~fmm_m2p_work fmm_m2p_work proc~fmm_m2p fmm_m2p proc~fmm_m2p->proc~fmm_m2p_work

Contents

Source Code


Source Code

subroutine fmm_m2p_work(c, src_r, p, vscales_rel, alpha, src_m, &
    & beta, dst_v, work)
    ! Inputs
    integer, intent(in) :: p
    real(rp), intent(in) :: c(3), src_r, vscales_rel((p+1)*(p+1)), alpha, &
        & src_m((p+1)*(p+1)), beta
    ! Output
    real(rp), intent(inout) :: dst_v
    ! Workspace
    real(rp), intent(out), target :: work(p+1)
    ! Local variables
    real(rp) :: rho, ctheta, stheta, cphi, sphi, rcoef, t, tmp, tmp1, tmp2, &
        & tmp3, max12, ssq12, pl2m, pl1m, plm, pmm, cmphi, smphi, ylm
    integer :: l, m, indl
    ! Scale output
    if (abs(beta) < eps_rp) then
        dst_v = 0.0_rp
    else
        dst_v = beta * dst_v
    end if
    ! In case of 0.0_rp alpha nothing else is required no matter what is the
    ! value of the induced potential
    if (abs(alpha) < eps_rp) then
        return
    end if
    ! Get spherical coordinates
    if (abs(c(1)) <  eps_rp) then
        max12 = abs(c(2))
        ssq12 = 1.0_rp
    else if (abs(c(2)) .gt. abs(c(1))) then
        max12 = abs(c(2))
        ssq12 = 1.0_rp + (c(1)/c(2))**2
    else
        max12 = abs(c(1))
        ssq12 = 1.0_rp + (c(2)/c(1))**2
    end if
    ! Then we compute rho
    if (abs(c(3)) < eps_rp) then
        rho = max12 * sqrt(ssq12)
    else if (abs(c(3)) .gt. max12) then
        rho = 1.0_rp + ssq12 *(max12/c(3))**2
        rho = abs(c(3)) * sqrt(rho)
    else
        rho = ssq12 + (c(3)/max12)**2
        rho = max12 * sqrt(rho)
    end if
    ! In case of a singularity (rho=0.0_rp) induced potential is infinite and is
    ! not taken into account.
    if (abs(rho) < eps_rp) then
        return
    end if
    ! Compute the actual induced potential
    rcoef = src_r / rho
    ! Length of a vector x(1:2)
    stheta = max12 * sqrt(ssq12)
    ! Case x(1:2) != 0
    if (abs(stheta) > eps_rp) then
        ! Normalize cphi and sphi
        cphi = c(1) / stheta
        sphi = c(2) / stheta
        ! Normalize ctheta and stheta
        ctheta = c(3) / rho
        stheta = stheta / rho
        ! Treat easy cases
        select case(p)
            case (0)
                ! l = 0
                dst_v = dst_v + alpha*rcoef*vscales_rel(1)*src_m(1)
                return
            case (1)
                ! l = 0
                tmp = src_m(1) * vscales_rel(1)
                ! l = 1
                tmp2 = ctheta * src_m(3) * vscales_rel(3)
                tmp3 = vscales_rel(4) * stheta
                tmp2 = tmp2 - tmp3*sphi*src_m(2)
                tmp2 = tmp2 - tmp3*cphi*src_m(4)
                dst_v = dst_v + alpha*rcoef*(tmp+rcoef*tmp2)
                return
        end select
        ! Now p>1
        ! Precompute alpha*rcoef^{l+1}
        work(1) = alpha * rcoef
        do l = 1, p
            work(l+1) = rcoef * work(l)
        end do
        ! Case m = 0
        ! P_{l-2}^m which is P_0^0 now
        pl2m = 1.0_rp
        dst_v = dst_v + work(1)*src_m(1)*vscales_rel(1)
        ! Update P_m^m for the next iteration
        pmm = -stheta
        ! P_{l-1}^m which is P_{m+1}^m now
        pl1m = ctheta
        ylm = pl1m * vscales_rel(3)
        dst_v = dst_v + work(2)*src_m(3)*ylm
        ! P_l^m for l>m+1
        do l = 2, p
            plm = dble(2*l-1)*ctheta*pl1m - dble(l-1)*pl2m
            plm = plm / dble(l)
            ylm = plm * vscales_rel(l*l+l+1)
            dst_v = dst_v + work(l+1)*src_m(l*l+l+1)*ylm
            ! Update P_{l-2}^m and P_{l-1}^m for the next iteration
            pl2m = pl1m
            pl1m = plm
        end do
        ! Prepare cos(m*phi) and sin(m*phi) for m=1
        cmphi = cphi
        smphi = sphi
        ! Case 0<m<p
        do m = 1, p-1
            ! P_{l-2}^m which is P_m^m now
            pl2m = pmm
            ylm = pmm * vscales_rel((m+1)**2)
            tmp1 = cmphi*src_m((m+1)**2) + smphi*src_m(m*m+1)
            dst_v = dst_v + work(m+1)*ylm*tmp1
            ! Temporary to reduce number of operations
            tmp1 = dble(2*m+1) * pmm
            ! Update P_m^m for the next iteration
            pmm = -stheta * tmp1
            ! P_{l-1}^m which is P_{m+1}^m now
            pl1m = ctheta * tmp1
            ylm = pl1m * vscales_rel((m+1)*(m+3))
            tmp1 = cmphi*src_m((m+1)*(m+3)) + smphi*src_m((m+1)*(m+2)+1-m)
            dst_v = dst_v + work(m+2)*ylm*tmp1
            ! P_l^m for l>m+1
            do l = m+2, p
                plm = dble(2*l-1)*ctheta*pl1m - dble(l+m-1)*pl2m
                plm = plm / dble(l-m)
                ylm = plm * vscales_rel(l*l+l+1+m)
                tmp1 = cmphi*src_m(l*l+l+1+m) + smphi*src_m(l*l+l+1-m)
                dst_v = dst_v + work(l+1)*ylm*tmp1
                ! Update P_{l-2}^m and P_{l-1}^m for the next iteration
                pl2m = pl1m
                pl1m = plm
            end do
            ! Update cos(m*phi) and sin(m*phi) for the next iteration
            tmp1 = cmphi
            cmphi = cmphi*cphi - smphi*sphi
            smphi = tmp1*sphi + smphi*cphi
        end do
        ! Case m=p requires only to use P_m^m
        ylm = pmm * vscales_rel((p+1)**2)
        tmp1 = cmphi*src_m((p+1)**2) + smphi*src_m(p*p+1)
        dst_v = dst_v + work(p+1)*ylm*tmp1
    ! Case of x(1:2) = 0 and x(3) != 0
    else
        ! In this case Y_l^m = 0 for m != 0, so only case m = 0 is taken into
        ! account. Y_l^0 = ctheta^l in this case where ctheta is either +1 or
        ! -1. So, we copy sign(ctheta) into rcoef. But before that we
        ! initialize alpha/r factor for l=0 case without taking possible sign
        ! into account.
        t = alpha * rcoef
        if (c(3) .lt. 0.0_rp) then
            rcoef = -rcoef
        end if
        ! Proceed with accumulation of a potential
        indl = 1
        do l = 0, p
            ! Index of Y_l^0
            indl = indl + 2*l
            ! Add 4*pi/(2*l+1)*rcoef^{l+1}*Y_l^0 contribution
            dst_v = dst_v + t*src_m(indl)*vscales_rel(indl)
            ! Update t
            t = t * rcoef
        end do
    end if
end subroutine fmm_m2p_work