mimic_long_range.F90 Source File


Contents

Source Code


Source Code

!
!    MiMiC: A Framework for Multiscale Modeling in Computational Chemistry
!    Copyright (C) 2015-2022  The MiMiC Contributors (see CONTRIBUTORS file for details).
!
!    This file is part of MiMiC.
!
!    MiMiC is free software: you can redistribute it and/or modify
!    it under the terms of the GNU Lesser General Public License as
!    published by the Free Software Foundation, either version 3 of
!    the License, or (at your option) any later version.
!
!    MiMiC is distributed in the hope that it will be useful, but
!    WITHOUT ANY WARRANTY; without even the implied warranty of
!    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
!    GNU Lesser General Public License for more details.
!
!    You should have received a copy of the GNU Lesser General Public License
!    along with this program.  If not, see <http://www.gnu.org/licenses/>.
!

!> author: Jógvan Magnus Haugaard Olsen
!> computes energy, forces, and potential from the interactions between the
!> quantum fragment and long-range atoms using a multipole expansion
!> approximation for the quantum fragment
module mimic_long_range

    use mimic_cells
    use mimic_errors
    use mimic_fragments
    use mimic_particles
    use mimic_precision, only: dp
    use mimic_tensors
    use mimic_utils, only: timer_start, timer_stop

    implicit none

    private

    public :: compute_lr_energy
    public :: compute_lr_potential
    public :: compute_lr_forces
    public :: compute_folded_tensors

contains

!> Computes folded tensor used in energy and potential computations
subroutine compute_folded_tensors(tensors, tensor_sums, &
                                  multipole_origin, multipole_order, &
                                  atoms)

    !> Array storing tensors
    real(dp), dimension(:), intent(out) :: tensors
    !> Array stroing tensor sums
    real(dp), dimension(:), intent(out) :: tensor_sums
    !> Coordinates of the origin of multipolar expansion
    real(dp), dimension(3), intent(in) :: multipole_origin
    !> Maximal order of multipoles
    integer, intent(in) :: multipole_order
    !> long-range atoms
    type(atom_type), dimension(:), intent(in) :: atoms

    integer :: i, j, k, order
    real(dp) :: taylor
    real(dp), dimension(3) :: r

    call timer_start("compute_folded_tensors")

    tensors = 0.0_dp
    tensor_sums = 0.0_dp

    !$OMP PARALLEL DO private(i, j, r, k, order, taylor, tensors) &
    !$OMP& reduction(+:tensor_sums)
    do i = 1, size(atoms)
        r = multipole_origin - atoms(i)%coordinate
        k = 0
        do order = 0, multipole_order
            j = k + 1
            k = k + tensor_size(order)
            call folded_interaction_tensor(r, tensors(j:k), order, 0)
            taylor = merge(1.0_dp, - 1.0_dp, (mod(order, 2) == 0)) / factorial(order)
            tensors(j:k) = taylor * tensors(j:k)
        end do
        tensor_sums = tensor_sums + atoms(i)%charge * tensors
    end do
    !$OMP END PARALLEL DO

    call timer_stop

end subroutine compute_folded_tensors

!> compute interaction energy between quantum fragment and long-range atoms
subroutine compute_lr_energy(quantum_fragment, &
                             energy, tensor_sums)

    !> quantum fragment
    type(quantum_fragment_type), intent(in) :: quantum_fragment
    !> interaction energy
    real(dp), intent(out) :: energy
    !> Array stroing tensor sums
    real(dp), dimension(:), intent(in) :: tensor_sums

    call timer_start("compute_lr_energy")

    energy = 0.0_dp

    energy = dot_product(quantum_fragment%nuclear_multipoles%values, tensor_sums)

    call timer_stop

end subroutine compute_lr_energy

!> compute potential from long-range atoms on quantum fragment
subroutine compute_lr_potential(quantum_fragment, &
                                a_start, a_end, tensor_sums)

    !> quantum fragment
    type(quantum_fragment_type), intent(in) :: quantum_fragment
    !> start index of real space mesh of lattice vector a
    integer, intent(in) :: a_start
    !> end index of real space mesh of lattice vector a
    integer, intent(in) :: a_end
    !> Array stroing tensor sums
    real(dp), dimension(:), intent(in) :: tensor_sums

    integer :: i, j, k
    integer :: x, y, z
    integer :: a, b, c
    integer :: a_index
    integer :: comp
    integer :: order
    integer :: multipole_order
    real(dp) :: temp
    real(dp), dimension(3) :: ra, rb, rc, r
    real(dp), dimension(3) :: multipole_origin
    real(dp), dimension(3,3) :: lattice_steps

    call timer_start("compute_lr_potential")

    if (a_start > a_end) then
        call handle_error(SEVERITY_FATAL, &
                          TYPE_INCORRECT_ARG, &
                          "a_start should be smaller than a_end", &
                          __FILE__, __LINE__)
    endif

    multipole_origin = quantum_fragment%nuclear_multipoles%origin
    multipole_order = quantum_fragment%nuclear_multipoles%order

    associate (cell => quantum_fragment%cell)

    do i = 1, 3
        lattice_steps(:,i) = cell%lattice(:,i) / real(cell%num_points(i), dp)
    end do

    associate (potential => quantum_fragment%potential%field)

    !$OMP PARALLEL DO private(c, rc, b, rb, a, a_index, ra, r, &
    !$OMP& comp, order, i, j, k, temp)
    do c = 1, cell%num_points(3)
        rc = cell%origin + real(c - 1, dp) * lattice_steps(:,3)
        do b = 1, cell%num_points(2)
            rb = rc + real(b - 1, dp) * lattice_steps(:,2)
            do a = a_start, a_end
                a_index = a - a_start + 1
                ra = rb + real(a - 1, dp) * lattice_steps(:,1)
                r = ra - multipole_origin
                comp = 1
                do order = 0, multipole_order
                    do i = order, 0, - 1
                        do j = order - i, 0, - 1
                            k = order - i - j
                            temp = 1.0_dp
                            do x = 1, i
                                temp = temp * r(1)
                            end do
                            do y = 1, j
                                temp = temp * r(2)
                            end do
                            do z = 1, k
                                temp = temp * r(3)
                            end do
                            potential(a_index,b,c) = potential(a_index,b,c) + temp * tensor_sums(comp)
                            comp = comp + 1
                        end do
                    end do
                end do
            end do
        end do
    end do
    !$OMP END PARALLEL DO

    end associate

    end associate

    call timer_stop

end subroutine compute_lr_potential

!> compute forces from long-range atoms on quantum fragment and vice versa
subroutine compute_lr_forces(atoms, quantum_fragment, &
                             atom_start, atom_end)

    !> list of atoms in the long-range part
    type(atom_type), dimension(:), intent(in) :: atoms
    !> the quantum fragment
    type(quantum_fragment_type), intent(in) :: quantum_fragment
    !> index of the first atom that current process will handle
    integer, intent(in) :: atom_start
    !> index of the last atom that the current process will handle
    integer, intent(in) :: atom_end

    integer :: i, j
    integer :: x, y, z
    integer :: tx, ty, tz
    integer :: mx, my, mz
    integer :: rx, ry, rz
    integer :: fx, fy, fz
    integer :: mi, fi, ti
    integer :: t_start, t_end, m_start, m_end
    integer :: rank_f, rank_t, rank_m
    integer :: order, expansion_order
    real(dp) :: coeff, temp
    real(dp), dimension(3) :: r
    real(dp), dimension(3) :: force
    real(dp), dimension(3) :: expansion_origin
    real(dp), dimension(:), allocatable :: multipoles
    real(dp), dimension(:), allocatable :: tensor, tensor_sum
    real(dp), dimension(:), allocatable :: polytensor, polytensor_sum

    call timer_start("compute_lr_forces")

    if (atom_start > atom_end) then
        call handle_error(SEVERITY_FATAL, &
                          TYPE_INCORRECT_ARG, &
                          "atom_start should be smaller than atom_end", &
                          __FILE__, __LINE__)
    endif

    expansion_origin = quantum_fragment%nuclear_multipoles%origin
    expansion_order = quantum_fragment%nuclear_multipoles%order

    allocate(multipoles(polytensor_size(expansion_order)))
    multipoles = quantum_fragment%nuclear_multipoles%values + &
                 quantum_fragment%electronic_multipoles%values

    allocate(tensor(tensor_size(expansion_order + 1)))
    allocate(tensor_sum(tensor_size(expansion_order + 1)))

    allocate(polytensor(polytensor_size(expansion_order)))
    allocate(polytensor_sum(polytensor_size(expansion_order)))

    !$OMP PARALLEL DO private(i, r, force, order, m_start, m_end, &
    !$OMP& rank_t, rank_m, rank_f, fx, fy, fz, fi, tx, ty, tz, ti, &
    !$OMP& mx, my, mz, mi, coeff, tensor)
    do i = atom_start, atom_end
        r = expansion_origin - atoms(i)%coordinate
        force = 0.0_dp
        m_end = 0
        do order = 0, expansion_order
            m_start = m_end + 1
            m_end = m_start + tensor_size(order) - 1
            call interaction_tensor(r, tensor(1:tensor_size(order+1)), order, 1)
            rank_t = tensor_rank(tensor(1:tensor_size(order+1)))
            rank_m = tensor_rank(multipoles(m_start:m_end))
            rank_f = tensor_rank(force)
            do fx = rank_f, 0, -1
                do fy = rank_f - fx, 0, -1
                    fz = rank_f - fx - fy
                    fi = convert_index(fx, fy, fz)
                    do tx = rank_t, fx, -1
                        do ty = rank_t - tx, fy, -1
                            tz = rank_t - tx - ty
                            if (tz < fz) cycle
                            ti = convert_index(tx, ty, tz)
                            mx = tx - fx
                            my = ty - fy
                            mz = tz - fz
                            mi = convert_index(mx, my, mz)
                            coeff = real((-1)**(tx+ty+tz), dp) / (factorial(mx) * factorial(my) * factorial(mz))
                            force(fi) = force(fi) + coeff * tensor(ti) * multipoles(m_start-1+mi)
                        end do
                    end do
                end do
            end do
        end do
        atoms(i)%force = atoms(i)%force - atoms(i)%charge * force
    end do
    !$OMP END PARALLEL DO

    tensor_sum = 0.0_dp
    !$OMP PARALLEL DO private(i, r, tensor) reduction(+:tensor_sum)
    do i = atom_start, atom_end
        r = expansion_origin - atoms(i)%coordinate
        call interaction_tensor(r, tensor, expansion_order, 1)
        tensor_sum = tensor_sum + atoms(i)%charge * tensor
    end do
    !$OMP END PARALLEL DO

    force = 0.0_dp
    m_start = polytensor_size(expansion_order - 1) + 1
    m_end = m_start + tensor_size(expansion_order) - 1
    rank_t = tensor_rank(tensor_sum)
    rank_m = tensor_rank(multipoles(m_start:m_end))
    rank_f = tensor_rank(force)
    do fx = rank_f, 0, -1
        do fy = rank_f - fx, 0, -1
            fz = rank_f - fx - fy
            fi = convert_index(fx, fy, fz)
            do tx = rank_t, fx, -1
                do ty = rank_t - tx, fy, -1
                    tz = rank_t - tx - ty
                    if (tz < fz) cycle
                    ti = convert_index(tx, ty, tz)
                    mx = tx - fx
                    my = ty - fy
                    mz = tz - fz
                    mi = convert_index(mx, my, mz)
                    coeff = real((-1)**(mx+my+mz), dp) / (factorial(mx) * factorial(my) * factorial(mz))
                    force(fi) = force(fi) + coeff * tensor_sum(ti) * multipoles(m_start-1+mi)
                end do
            end do
        end do
    end do

    force = force / real(quantum_fragment%num_nuclei, dp)
    do j = 1, quantum_fragment%num_nuclei
        quantum_fragment%nuclei(j)%force = quantum_fragment%nuclei(j)%force - force
    end do

    polytensor = 0.0_dp
    polytensor_sum = 0.0_dp
    !$OMP PARALLEL DO private(i, r, order, t_start, t_end, polytensor) &
    !$OMP& reduction(+:polytensor_sum)
    do i = atom_start, atom_end
        r = expansion_origin - atoms(i)%coordinate
        t_end = 1
        do order = 1, expansion_order
            t_start = t_end + 1
            t_end = t_start + tensor_size(order) - 1
            call interaction_tensor(r, polytensor(t_start:t_end), order, 0)
        end do
        polytensor_sum = polytensor_sum + atoms(i)%charge * polytensor
    end do
    !$OMP END PARALLEL DO

    !$OMP PARALLEL DO private(j, r, multipoles, order, rx, ry, rz, temp, &
    !$OMP& force, t_start, t_end, m_start, m_end, rank_t, rank_m, rank_f, &
    !$OMP& fx, fy, fz, fi, tx, ty, tz, ti, mx, my, mz, mi, coeff)
    do j = 1, quantum_fragment%num_nuclei
        r = quantum_fragment%nuclei(j)%coordinate - expansion_origin
        multipoles = 0.0_dp
        mi = 1
        do order = 0, expansion_order - 1
            do rx = order, 0, -1
                do ry = order - rx, 0, -1
                    rz = order - rx - ry
                    temp = quantum_fragment%nuclei(j)%charge
                    do x = 1, rx
                        temp = temp * r(1)
                    end do
                    do y = 1, ry
                        temp = temp * r(2)
                    end do
                    do z = 1, rz
                        temp = temp * r(3)
                    end do
                    multipoles(mi) = multipoles(mi) + temp
                    mi = mi + 1
                end do
            end do
        end do
        force = 0.0_dp
        t_end = 1
        m_end = 0
        do order = 1, expansion_order
            t_start = t_end + 1
            t_end = t_start + tensor_size(order) - 1
            m_start = m_end + 1
            m_end = m_start + tensor_size(order-1) - 1
            rank_t = tensor_rank(polytensor_sum(t_start:t_end))
            rank_m = tensor_rank(multipoles(m_start:m_end))
            rank_f = tensor_rank(force)
            do fx = rank_f, 0, -1
                do fy = rank_f - fx, 0, -1
                    fz = rank_f - fx - fy
                    fi = convert_index(fx, fy, fz)
                    do tx = rank_t, fx, -1
                        do ty = rank_t - tx, fy, -1
                            tz = rank_t - tx - ty
                            if (tz < fz) cycle
                            ti = convert_index(tx, ty, tz)
                            mx = tx - fx
                            my = ty - fy
                            mz = tz - fz
                            mi = convert_index(mx, my, mz)
                            coeff = real((-1)**(tx+ty+tz), dp) / (factorial(mx) * factorial(my) * factorial(mz))
                            force(fi) = force(fi) + coeff * polytensor_sum(t_start-1+ti) * multipoles(m_start-1+mi)
                        end do
                    end do
                end do
            end do
        end do
        quantum_fragment%nuclei(j)%force = quantum_fragment%nuclei(j)%force - force
    end do
    !$OMP END PARALLEL DO

    deallocate(multipoles)
    deallocate(tensor)
    deallocate(tensor_sum)
    deallocate(polytensor)
    deallocate(polytensor_sum)

    call timer_stop

end subroutine compute_lr_forces

end module mimic_long_range