mirror of https://gitlab.com/QEF/q-e.git
2518 lines
103 KiB
Fortran
2518 lines
103 KiB
Fortran
!
|
|
! Copyright (C) 2001-2009 Quantum ESPRESSO group
|
|
! Copyright (C) 2009 Brian Kolb, Timo Thonhauser - Wake Forest University
|
|
! This file is distributed under the terms of the
|
|
! GNU General Public License. See the file `License'
|
|
! in the root directory of the present distribution,
|
|
! or http://www.gnu.org/copyleft/gpl.txt .
|
|
!
|
|
!----------------------------------------------------------------------------
|
|
|
|
#define FFTGRADIENT
|
|
!#undef FFTGRADIENT
|
|
|
|
MODULE vdW_DF
|
|
|
|
!! This module calculates the non-local correlation contribution to the energy
|
|
!! and potential. This method is based on the method of Guillermo Roman-Perez
|
|
!! and Jose M. Soler described in:
|
|
!!
|
|
!! G. Roman-Perez and J. M. Soler, PRL 103, 096101 (2009)
|
|
!!
|
|
!! henceforth referred to as SOLER. That method is a new implementation
|
|
!! of the method found in:
|
|
!!
|
|
!! M. Dion, H. Rydberg, E. Schroeder, D. C. Langreth, and
|
|
!! B. I. Lundqvist, Phys. Rev. Lett. 92, 246401 (2004).
|
|
!!
|
|
!! henceforth referred to as DION. Further information about the
|
|
!! functional and its corresponding potential can be found in:
|
|
!!
|
|
!! T. Thonhauser, V.R. Cooper, S. Li, A. Puzder, P. Hyldgaard,
|
|
!! and D.C. Langreth, Phys. Rev. B 76, 125112 (2007).
|
|
!!
|
|
!! A review article that shows many of the applications vdW-DF has been
|
|
!! applied to so far can be found at:
|
|
!!
|
|
!! D. C. Langreth et al., J. Phys.: Condens. Matter 21, 084203 (2009).
|
|
!!
|
|
!! There are a number of subroutines in this file. All are used only
|
|
!! by other subroutines here except for the xc_vdW_DF subroutine
|
|
!! which is the driver routine for the vdW-DF calculations and is called
|
|
!! from v_of_rho. This routine handles setting up the parallel run (if
|
|
!! any) and carries out the calls necessary to calculate the non-local
|
|
!! correlation contributions to the energy and potential.
|
|
|
|
USE kinds, ONLY : dp
|
|
USE constants, ONLY : pi, e2
|
|
USE kernel_table, ONLY : q_mesh, Nr_points, Nqs, r_max
|
|
USE mp, ONLY : mp_bcast, mp_sum, mp_barrier, mp_bcast_cv
|
|
USE mp_global, ONLY : me_pool, nproc_pool, intra_pool_comm, root_pool
|
|
USE io_global, ONLY : ionode
|
|
USE fft_base, ONLY : dfftp
|
|
USE fft_interfaces, ONLY : fwfft, invfft
|
|
USE control_flags, ONLY : iverbosity, gamma_only
|
|
USE io_global, ONLY : stdout
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), PARAMETER :: epsr =1.d-12 ! a small number to cut off points with negative or undefined densities
|
|
integer :: vdw_type = 1
|
|
|
|
private
|
|
public :: xc_vdW_DF, stress_vdW_DF, interpolate_kernel, print_sigma, dv_drho_vdw, vdw_type
|
|
|
|
CONTAINS
|
|
|
|
!! #################################################################################################
|
|
!! | |
|
|
!! | XC_VDW_DF |
|
|
!! |_____________|
|
|
|
|
SUBROUTINE xc_vdW_DF(rho_valence, rho_core, etxc, vtxc, v)
|
|
|
|
!! Modules to include
|
|
!! -------------------------------------------------------------------------
|
|
|
|
use gvect, ONLY : ngm, nl, g, nlm
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : omega, tpiba
|
|
USE fft_scalar, ONLY : cfft3d
|
|
!! -------------------------------------------------------------------------
|
|
|
|
!! Local variables
|
|
!! ----------------------------------------------------------------------------------
|
|
! _
|
|
real(dp), intent(IN) :: rho_valence(:,:) !
|
|
real(dp), intent(IN) :: rho_core(:) ! PWSCF input variables
|
|
real(dp), intent(inout) :: etxc, vtxc, v(:,:) !_
|
|
|
|
|
|
integer :: i_grid, theta_i, i_proc, I !! Indexing variables over grid points,
|
|
! !! theta functions, and processors, and a
|
|
! !! generic index.
|
|
|
|
real(dp) :: grid_cell_volume !! The volume of the unit cell per G-grid point
|
|
|
|
real(dp), allocatable :: q0(:) !! The saturated value of q (equations 11 and 12 of DION)
|
|
! !! This saturation is that of equation 7 in
|
|
! !! SOLER
|
|
|
|
real(dp), allocatable :: gradient_rho(:,:) !! The gradient of the charge density. The
|
|
! !! format is as follows:
|
|
! !! gradient_rho(grid_point, cartesian_component)
|
|
|
|
real(dp), allocatable :: potential(:) !! The vdW contribution to the potential
|
|
|
|
real(dp), allocatable :: dq0_drho(:) !! The derivative of the saturated q0
|
|
! !! (equation 7 of SOLER) with respect
|
|
! !! to the charge density (sort of. see
|
|
! !! get_q0_on_grid subroutine below.)
|
|
|
|
real(dp), allocatable :: dq0_dgradrho(:) !! The derivative of the saturated q0
|
|
! !! (equation 7 of SOLER) with respect
|
|
! !! to the gradient of the charge density
|
|
! !! (again, see get_q0_on_grid subroutine)
|
|
|
|
complex(dp), allocatable :: thetas(:,:) !! These are the functions of equation 11 of
|
|
! !! SOLER. They will be forward Fourier transformed
|
|
! !! in place to get theta(k) and worked on in
|
|
! !! place to get the u_alpha(r) of equation 14
|
|
! !! in SOLER. They are formatted as follows:
|
|
! !1 thetas(grid_point, theta_i)
|
|
|
|
real(dp) :: Ec_nl !! The non-local vdW contribution to the energy
|
|
|
|
real(dp), allocatable :: total_rho(:) !! This is the sum of the valence and core
|
|
! !! charge. This just holds the piece assigned
|
|
! !! to this processor.
|
|
#ifndef FFTGRADIENT
|
|
integer, parameter :: Nneighbors = 4 !! How many neighbors on each side
|
|
! !! to include in numerical derivatives.
|
|
! !! Can be from 1 to 6
|
|
|
|
real(dp), allocatable :: full_rho(:) !! This is the whole charge density. It
|
|
! !! is the sum of valence and core density
|
|
! !! over the entire simulation cell. Each
|
|
! !! processor has a copy of this to do the
|
|
! !! numerical gradients.
|
|
integer, ave :: msy_start_z, my_end_z !! Starting and ending z-slabs for this processor
|
|
|
|
|
|
integer, allocatable, save :: procs_Npoints(:) !! The number of grid points assigned to each proc
|
|
integer, allocatable, save :: procs_start(:) !! The first assigned index into the charge-density array for each proc
|
|
integer, allocatable, save :: procs_end(:) !! The last assigned index into the charge density array for each proc
|
|
|
|
#endif
|
|
|
|
logical, save :: first_iteration = .true. !! Whether this is the first time this
|
|
! !! routine has been called.
|
|
|
|
!! ---------------------------------------------------------------------------------------------
|
|
!! Begin calculations
|
|
|
|
!! Check to make sure we aren't trying to do a spin-polarized run
|
|
!! Gamma point calculations can be done using the special {gamma} features
|
|
!! stress tensor calcultion and cell relaxation run are also possible.
|
|
!! --------------------------------------------------------------------------------------------------------
|
|
|
|
call errore('xc_vdW_DF','vdW functional not implemented for spin polarized runs', size(rho_valence,2)-1)
|
|
|
|
!! --------------------------------------------------------------------------------------------------------
|
|
|
|
if (first_iteration) then
|
|
|
|
#ifndef FFTGRADIENT
|
|
!! Here we set up the calculations on the first iteration. If this is a parallel run, each
|
|
!! processor figures out which element in the charge-density array it should start and stop on.
|
|
!! PWSCF splits the cell up into slabs in the z-direction to distribute over processors.
|
|
!! Thus, each processor figures out what z-planes its region corresponds to. That is important
|
|
!! for the get_3d_indices and get_potential subroutines below.
|
|
!! --------------------------------------------------------------------------------------------------
|
|
|
|
|
|
allocate( procs_Npoints(0:nproc_pool-1), procs_start(0:nproc_pool-1), procs_end(0:nproc_pool-1) )
|
|
|
|
procs_Npoints(me_pool) = dfftp%nnr
|
|
procs_start(0) = 1
|
|
|
|
! All processors communicate how many points they have been assigned. Each processor
|
|
! then calculates for itself what the starting and ending indices should be for every
|
|
! other processor.
|
|
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
do i_proc = 0, nproc_pool-1
|
|
|
|
call mp_bcast(procs_Npoints(i_proc), i_proc, intra_pool_comm)
|
|
call mp_barrier(intra_pool_comm)
|
|
|
|
procs_end(i_proc) = procs_start(i_proc) + procs_Npoints(i_proc) - 1
|
|
|
|
if (i_proc .ne. nproc_pool-1) then
|
|
|
|
procs_start(i_proc+1) = procs_end(i_proc)+1
|
|
|
|
end if
|
|
|
|
end do
|
|
|
|
!++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
! Each processor finds the starting and ending z-planes assined to them. Since
|
|
! PWSCF splits the cell into slabs in the z-direction, the beginning (ending)
|
|
! z slabs can be found by dividing the starting (ending) index into the charge density
|
|
! array by the number of points in a slab of thickness 1. We add 1 to the starting
|
|
! z plane because of the integer division and the fact that arrays in Fortran start at 1.
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
my_start_z = procs_start(me_pool)/(dfftp%nr1x*dfftp%nr2x)+1
|
|
my_end_z = procs_end(me_pool)/(dfftp%nr1x*dfftp%nr2x)
|
|
|
|
!write(*,'(A,3I5)') "Parall en [proc, my_start_z, my_end_z]", me_pool, my_start_z, my_end_z
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
#endif
|
|
first_iteration = .false.
|
|
|
|
!! Here we output some of the parameters being used in the run. This is important because
|
|
!! these parameters are read from the vdW_kernel_table file. The user should ensure that
|
|
!! these are the parameters they were intending to use on each run.
|
|
|
|
!! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
if (ionode ) then
|
|
|
|
write(*,'(/ /A )') "---------------------------------------------------------------------------------"
|
|
write(*,'(A /)') "Carrying out vdW-DF run using the following parameters:"
|
|
|
|
write(*,'(A,I6,A,I6,A,F8.3)') "Nqs = ",Nqs, " Nr_points = ", Nr_points," r_max = ",r_max
|
|
|
|
write(*,'(A)',advance='no') "q_mesh = "
|
|
write(*,'(F15.8)') (q_mesh(I), I=1, Nqs)
|
|
|
|
#ifdef FFTGRADIENT
|
|
write(*,'(/ A )') "Gradients computed in Reciprocal space"
|
|
#else
|
|
write(*,'(/ A )') "Gradients computed in Real space"
|
|
#endif
|
|
write(*,'(/ A / /)') "---------------------------------------------------------------------------------"
|
|
|
|
|
|
end if
|
|
|
|
!! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
end if
|
|
|
|
!! --------------------------------------------------------------------------------------------------
|
|
|
|
!! Allocate arrays. nnr is a PWSCF variable that holds the number of points assigned to
|
|
!! a given processor.
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
allocate( q0(dfftp%nnr) )
|
|
allocate( gradient_rho(dfftp%nnr, 3) )
|
|
allocate( dq0_drho(dfftp%nnr), dq0_dgradrho(dfftp%nnr) )
|
|
allocate( total_rho(dfftp%nnr) )
|
|
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
!! Add together the valence and core charge densities to get the total charge density
|
|
total_rho = rho_valence(:,1) + rho_core(:)
|
|
|
|
#ifdef FFTGRADIENT
|
|
!! -------------------------------------------------------------------------
|
|
!! Here we calculate the gradient in reciprocal space using FFT
|
|
!! -------------------------------------------------------------------------
|
|
call numerical_gradient(total_rho,gradient_rho)
|
|
|
|
#else
|
|
!! -------------------------------------------------------------------------
|
|
!! Here we calculate the gradient numerically in real spacee
|
|
!! The Nneighbors variable is set above and gives the number of points in
|
|
!! each direction to consider when taking the numerical derivatives.
|
|
!! -------------------------------------------------------------------------
|
|
!! If there is only 1 processor the needed information is held by the
|
|
!! total_rho array, otherwise we need to allocate the full_rho array that
|
|
!! will be deallocated the call since it is no longer needed.
|
|
!!
|
|
!! The full_rho array holds the charge density at every point in the
|
|
!! simulation cell.
|
|
!! Each processor needs this because the numerical gradients require
|
|
!! knowledge of the !! charge density on points outside the slab one has
|
|
!! been given. We don't allocate this in the case of using a single
|
|
!! processor since total_rho would already hold this information.
|
|
!! nr1x, nr2x, and nr3x are PWSCF variables that hold the TOTAL number of
|
|
!! divisions along each lattice vector. Thus, their product is the total
|
|
!! number of points in the cell (not just those assigned to a particular
|
|
!! processor).
|
|
!! ------------------------------------------------------------------------
|
|
|
|
if (nproc_pool > 1) then
|
|
|
|
allocate( full_rho(dfftp%nr1x*dfftp%nr2x*dfftp%nr3x) )
|
|
|
|
full_rho(procs_start(me_pool):procs_end(me_pool)) = total_rho
|
|
|
|
! All the processors broadcast their piece of the charge density to fill in the full_rho
|
|
! arrays of all processors
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
do i_proc = 0, nproc_pool - 1
|
|
|
|
call mp_barrier(intra_pool_comm)
|
|
call mp_bcast(full_rho(procs_start(i_proc):procs_end(i_proc)), i_proc, intra_pool_comm)
|
|
|
|
end do
|
|
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
! Here we calculate the gradient numerically in real spacee
|
|
|
|
call numerical_gradient(full_rho, Nneighbors, gradient_rho, my_start_z, my_end_z)
|
|
deallocate(full_rho)
|
|
|
|
else
|
|
|
|
! Here we calculate the gradient numerically in real spacee
|
|
call numerical_gradient(total_rho, Nneighbors, gradient_rho, my_start_z, my_end_z)
|
|
|
|
end if
|
|
|
|
#endif
|
|
!! -------------------------------------------------------------------------------------------------------------
|
|
|
|
!! Find the value of q0 for all assigned grid points. q is defined in equations
|
|
!! 11 and 12 of DION and q0 is the saturated version of q defined in equation
|
|
!! 7 of SOLER. This routine also returns the derivatives of the q0s with respect
|
|
!! to the charge-density and the gradient of the charge-density. These are needed
|
|
!! for the potential calculated below.
|
|
!! ---------------------------------------------------------------------------------
|
|
|
|
CALL get_q0_on_grid(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho)
|
|
|
|
!! ---------------------------------------------------------------------------------
|
|
|
|
!! Here we allocate and calculate the theta functions of SOLER equation 11. Thee are defined as
|
|
!! rho * P_i(q0(rho, gradient_rho)) where P_i is a polynomial that interpolates a Kroneker delta
|
|
!! function at the point q_i (taken from the q_mesh) and q0 is the saturated version of q. q is
|
|
!! defined in equations 11 and 12 of DION and the saturation proceedure is defined in equation 7
|
|
!! of soler. This is the biggest memory consumer in the method since the thetas array is
|
|
!! (total # of FFT points)*Nqs complex numbers. In a parallel run, each processor will hold the
|
|
!! values of all the theta functions on just the points assigned to it.
|
|
!! --------------------------------------------------------------------------------------------------
|
|
!! thetas are stored in reciprocal space as theta_i(k) because this is the way they are used later
|
|
!! for the convolution (equation 11 of SOLER). The ffts used here are timed.
|
|
!! --------------------------------------------------------------------------------------------------
|
|
|
|
allocate( thetas(dfftp%nnr, Nqs) )
|
|
CALL get_thetas_on_grid(total_rho, q0, thetas)
|
|
!! ---------------------------------------------------------------------------------------------
|
|
|
|
!! Carry out the integration in equation 7 of SOLER. This also turns the thetas array into the
|
|
!! precursor to the u_i(k) array which is inverse fourier transformed to get the u_i(r) functions
|
|
!! of SOLER equation 14. Add the energy we find to the output variable etxc. This process is timed.
|
|
!! --------------------------------------------------------------------------------------------------
|
|
|
|
call start_clock( 'vdW_energy')
|
|
|
|
call vdW_energy(thetas, Ec_nl)
|
|
|
|
etxc = etxc + Ec_nl
|
|
|
|
call stop_clock( 'vdW_energy')
|
|
|
|
!! --------------------------------------------------------------------------------------------------
|
|
|
|
!! If verbosity is set to high we output the total non-local correlation energy found
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
if (iverbosity > 0) then
|
|
|
|
call mp_sum(Ec_nl,intra_pool_comm)
|
|
|
|
if (ionode) write(*,'(/ / A /)') " ----------------------------------------------------------------"
|
|
|
|
if (ionode) write(*,'(A, F22.15 /)') " Non-local correlation energy = ", Ec_nl
|
|
|
|
if (ionode) write(*,'(A /)') " ----------------------------------------------------------------"
|
|
|
|
end if
|
|
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
!! Inverse Fourier transform the u_i(k) to get the u_i(r) of SOLER equation 14. These FFTs
|
|
!! are also timed and added to the timing of the forward FFTs done earlier.
|
|
!!---------------------------------------------------------------------------------------
|
|
|
|
call start_clock( 'vdW_ffts')
|
|
|
|
do theta_i = 1, Nqs
|
|
|
|
!call cft3(thetas(:,theta_i), dfftp%nr1, dfftp%nr2, dfftp%nr3, dfftp%nr1x, dfftp%nr2x, dfftp%nr3x, 1)
|
|
CALL invfft('Dense', thetas(:,theta_i), dfftp)
|
|
end do
|
|
|
|
call stop_clock( 'vdW_ffts')
|
|
|
|
!! -------------------------------------------------------------------------
|
|
|
|
!! Here we allocate the array to hold the potential. This is calculated via
|
|
!! equation 13 of SOLER, using the u_i(r) calculated from quations 14 and
|
|
!! 15 of SOLER. Each processor allocates the array to be the size of the
|
|
!! full grid because, as can be seen in SOLER equation 13, processors need
|
|
!! to access grid points outside their allocated regions.
|
|
!! This process is timed. The timer is stopped below after the v output
|
|
!! variable has been updated with the non-local corelation potential.
|
|
!! That is, the timer includes the communication time necessary in a
|
|
!! parallel run.
|
|
!! -------------------------------------------------------------------------
|
|
|
|
#ifdef FFTGRADIENT
|
|
|
|
call start_clock( 'vdW_v' )
|
|
|
|
allocate( potential(dfftp%nnr) )
|
|
|
|
call get_potential(q0, dq0_drho, dq0_dgradrho, gradient_rho, thetas, potential)
|
|
|
|
!! -------------------------------------------------------------------------
|
|
|
|
|
|
v(:,1) = v(:,1) + e2*potential(:)
|
|
|
|
call stop_clock( 'vdW_v' )
|
|
|
|
!! -----------------------------------------------------------------------
|
|
|
|
!! The integral of rho(r)*potential(r) for the vtxc output variable
|
|
!! --------------------------------------------------------------------
|
|
|
|
grid_cell_volume = omega/(dfftp%nr1*dfftp%nr2*dfftp%nr3)
|
|
|
|
do i_grid = 1, dfftp%nnr
|
|
|
|
vtxc = vtxc + e2*grid_cell_volume*rho_valence(i_grid,1)*potential(i_grid)
|
|
|
|
end do
|
|
|
|
deallocate(potential)
|
|
|
|
#else
|
|
|
|
call start_clock( 'vdW_v' )
|
|
|
|
allocate( potential(dfftp%nr1x*dfftp%nr2x*dfftp%nr3x) )
|
|
|
|
call get_potential(q0, dq0_drho, dq0_dgradrho, Nneighbors, gradient_rho, thetas, potential, my_start_z, my_end_z)
|
|
|
|
!! -------------------------------------------------------------------------
|
|
|
|
|
|
!! Reduction process to sum all the potentials of all the processors.
|
|
!! ----------------------------------------------------------------------
|
|
|
|
! call mp_barrier( intra_pool_comm )
|
|
call mp_sum(potential, intra_pool_comm)
|
|
|
|
!! ----------------------------------------------------------------------
|
|
|
|
!! Here, the potential is rebroadcast. Since each processor has part of the output v array it is easier if
|
|
!! each processor adds only its assigned points to the v array. After this step, however, all
|
|
!! processors hold the vdW potential over the entire grid.
|
|
!! ------------------------------------------------------------------------------------------------------
|
|
|
|
! call mp_barrier( intra_pool_comm )
|
|
call mp_bcast(potential, root_pool, intra_pool_comm)
|
|
|
|
!! ------------------------------------------------------------------------------------------------------
|
|
|
|
!! Each processor adds its piece of the potential to the output v array.
|
|
!! Stop the timer for the potential.
|
|
!! -----------------------------------------------------------------------
|
|
|
|
v(:,1) = v(:,1) + e2*potential(procs_start(me_pool):procs_end(me_pool))
|
|
|
|
call stop_clock( 'vdW_v' )
|
|
|
|
!! -----------------------------------------------------------------------
|
|
|
|
!! The integral of rho(r)*potential(r) for the vtxc output variable
|
|
!! --------------------------------------------------------------------
|
|
|
|
grid_cell_volume = omega/(dfftp%nr1x*dfftp%nr2x*dfftp%nr3x)
|
|
|
|
do i_grid = 1, dfftp%nnr
|
|
|
|
vtxc = vtxc + e2*grid_cell_volume * total_rho(i_grid)*potential(procs_start(me_pool)+i_grid-1)
|
|
|
|
end do
|
|
|
|
deallocate(potential)
|
|
|
|
#endif
|
|
!! ----------------------------------------------------------------------
|
|
|
|
!! Deallocate all arrays.
|
|
deallocate(q0, gradient_rho, dq0_drho, dq0_dgradrho, total_rho, thetas)
|
|
|
|
END SUBROUTINE xc_vdW_DF
|
|
|
|
!! #################################################################################################
|
|
!! | |
|
|
!! | STRESS_VDW_DF |
|
|
!! |_________________|
|
|
|
|
SUBROUTINE stress_vdW_DF(rho_valence, rho_core, sigma)
|
|
|
|
USE fft_base, ONLY : dfftp
|
|
use gvect, ONLY : ngm, nl, g, nlm
|
|
USE cell_base, ONLY : tpiba
|
|
|
|
implicit none
|
|
|
|
real(dp), intent(IN) :: rho_valence(:,:) !
|
|
real(dp), intent(IN) :: rho_core(:) ! Input variables
|
|
real(dp), intent(inout) :: sigma(3,3) !
|
|
|
|
real(dp), allocatable :: gradient_rho(:,:) !
|
|
real(dp), allocatable :: total_rho(:) ! Rho values
|
|
|
|
real(dp), allocatable :: q0(:) !
|
|
real(dp), allocatable :: dq0_drho(:) ! Q-values
|
|
real(dp), allocatable :: dq0_dgradrho(:) !
|
|
|
|
complex(dp), allocatable :: thetas(:,:) ! Thetas
|
|
#ifndef FFTGRADIENT
|
|
real(dp), allocatable :: full_rho(:) ! additional Rho values onthe full grid
|
|
|
|
integer, save :: my_start_z, my_end_z !
|
|
integer, allocatable, save :: procs_Npoints(:) !
|
|
integer, allocatable, save :: procs_start(:) !
|
|
integer, allocatable, save :: procs_end(:) !
|
|
|
|
logical, save :: first_stress_iteration = .true. !
|
|
integer :: Nneighbors = 4
|
|
#endif
|
|
integer :: i_proc, theta_i, l, m
|
|
|
|
real(dp) :: sigma_grad(3,3)
|
|
real(dp) :: sigma_ker(3,3)
|
|
|
|
!! ---------------------------------------------------------------------------------------------
|
|
!! Tests
|
|
!! --------------------------------------------------------------------------------------------------------
|
|
|
|
call errore('xc_vdW_DF','vdW functional not implemented for spin polarized runs', size(rho_valence,2)-1)
|
|
!IF ( gamma_only) CALL errore ('xc_vdW_DF', &
|
|
! & 'vdW functional not implemented for gamma point calculations. &
|
|
! & Use kpoints automatic and specify the gamma point explicitly', 2)
|
|
sigma(:,:) = 0.0_DP
|
|
sigma_grad(:,:) = 0.0_DP
|
|
sigma_ker(:,:) = 0.0_DP
|
|
|
|
#ifndef FFTGRADIENT
|
|
!! ---------------------------------------------------------------------------------------------
|
|
!! Parallel setup
|
|
!! ---------------------------------------------------------------------------
|
|
if (first_stress_iteration) then
|
|
|
|
allocate( procs_Npoints(0:nproc_pool-1), procs_start(0:nproc_pool-1), procs_end(0:nproc_pool-1) )
|
|
|
|
procs_Npoints(me_pool) = dfftp%nnr
|
|
procs_start(0) = 1
|
|
|
|
do i_proc = 0, nproc_pool-1
|
|
|
|
call mp_bcast(procs_Npoints(i_proc), i_proc, intra_pool_comm)
|
|
call mp_barrier(intra_pool_comm)
|
|
|
|
procs_end(i_proc) = procs_start(i_proc) + procs_Npoints(i_proc) - 1
|
|
|
|
if (i_proc .ne. nproc_pool-1) then
|
|
procs_start(i_proc+1) = procs_end(i_proc)+1
|
|
end if
|
|
|
|
end do
|
|
|
|
my_start_z = procs_start(me_pool)/(dfftp%nr1x*dfftp%nr2x)+1
|
|
my_end_z = procs_end(me_pool)/(dfftp%nr1x*dfftp%nr2x)
|
|
|
|
!write(*,'(A,3I5)') "Parall stress [proc, my_start_z, my_end_z]", me_pool, my_start_z, my_end_z
|
|
|
|
first_stress_iteration = .false.
|
|
|
|
end if
|
|
#endif
|
|
!! ---------------------------------------------------------------------------------------
|
|
!! Allocations
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
allocate( gradient_rho(dfftp%nnr, 3) )
|
|
allocate( total_rho(dfftp%nnr) )
|
|
allocate( q0(dfftp%nnr) )
|
|
allocate( dq0_drho(dfftp%nnr), dq0_dgradrho(dfftp%nnr) )
|
|
allocate( thetas(dfftp%nnr, Nqs) )
|
|
|
|
!! ---------------------------------------------------------------------------------------
|
|
!! Charge
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
total_rho = rho_valence(:,1) + rho_core(:)
|
|
|
|
#ifdef FFTGRADIENT
|
|
!! -------------------------------------------------------------------------
|
|
!! Here we calculate the gradient in reciprocal space using FFT
|
|
!! -------------------------------------------------------------------------
|
|
call numerical_gradient(total_rho,gradient_rho)
|
|
#else
|
|
!! ---------------------------------------------------------------------------------------
|
|
!! Here we calculate the gradient in Real space
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
if (nproc_pool > 1) then
|
|
|
|
allocate( full_rho(dfftp%nr1x*dfftp%nr2x*dfftp%nr3x) )
|
|
|
|
full_rho(procs_start(me_pool):procs_end(me_pool)) = total_rho
|
|
|
|
do i_proc = 0, nproc_pool - 1
|
|
|
|
call mp_barrier(intra_pool_comm)
|
|
call mp_bcast(full_rho(procs_start(i_proc):procs_end(i_proc)), i_proc, intra_pool_comm)
|
|
|
|
end do
|
|
|
|
call numerical_gradient(full_rho, Nneighbors, gradient_rho, my_start_z, my_end_z)
|
|
|
|
deallocate(full_rho)
|
|
|
|
else
|
|
|
|
call numerical_gradient(total_rho, Nneighbors, gradient_rho, my_start_z, my_end_z)
|
|
|
|
end if
|
|
#endif
|
|
!! -------------------------------------------------------------------------------------------------------------
|
|
!! Get q0.
|
|
!! ---------------------------------------------------------------------------------
|
|
|
|
CALL get_q0_on_grid(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho)
|
|
|
|
!! ---------------------------------------------------------------------------------
|
|
!! Get thetas in reciprocal space.
|
|
!! ---------------------------------------------------------------------------------
|
|
|
|
CALL get_thetas_on_grid(total_rho, q0, thetas)
|
|
|
|
!! ---------------------------------------------------------------------------------------
|
|
!! Stress
|
|
!! ---------------------------------------------------------------------------------------
|
|
CALL stress_vdW_DF_gradient(total_rho, gradient_rho, q0, dq0_drho, &
|
|
dq0_dgradrho, thetas, sigma_grad)
|
|
CALL print_sigma(sigma_grad, "VDW GRADIENT")
|
|
|
|
CALL stress_vdW_DF_kernel(total_rho, q0, thetas, sigma_ker)
|
|
CALL print_sigma(sigma_ker, "VDW KERNEL")
|
|
|
|
sigma = - (sigma_grad + sigma_ker)
|
|
|
|
do l = 1, 3
|
|
do m = 1, l - 1
|
|
sigma (m, l) = sigma (l, m)
|
|
enddo
|
|
enddo
|
|
|
|
CALL print_sigma(sigma, "VDW ALL")
|
|
|
|
deallocate( gradient_rho, total_rho, q0, dq0_drho, dq0_dgradrho, thetas )
|
|
|
|
END SUBROUTINE stress_vdW_DF
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | STRESS_VDW_DF_GRADIENT |
|
|
!! | |
|
|
|
|
SUBROUTINE stress_vdW_DF_gradient (total_rho, gradient_rho, q0, dq0_drho, &
|
|
dq0_dgradrho, thetas, sigma)
|
|
|
|
!!-----------------------------------------------------------------------------------
|
|
!! Modules to include
|
|
!! ----------------------------------------------------------------------------------
|
|
use gvect, ONLY : ngm, nl, g, nlm, nl, gg, igtongl, &
|
|
gl, ngl, gstart
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : omega, tpiba, alat, at, tpiba2
|
|
USE fft_scalar, ONLY : cfft3d
|
|
|
|
!! ----------------------------------------------------------------------------------
|
|
|
|
implicit none
|
|
|
|
real(dp), intent(IN) :: total_rho(:) !
|
|
real(dp), intent(IN) :: gradient_rho(:, :) ! Input variables
|
|
real(dp), intent(inout) :: sigma(:,:) !
|
|
real(dp), intent(IN) :: q0(:) !
|
|
real(dp), intent(IN) :: dq0_drho(:) !
|
|
real(dp), intent(IN) :: dq0_dgradrho(:) !
|
|
complex(dp), intent(IN) :: thetas(:,:) !
|
|
|
|
complex(dp), allocatable :: u_vdW(:,:) !
|
|
|
|
real(dp), allocatable :: d2y_dx2(:,:) !
|
|
real(dp) :: y(Nqs), dP_dq0, P, a, b, c, d, e, f ! Interpolation
|
|
real(dp) :: dq !
|
|
|
|
integer :: q_low, q_hi, q, q1_i, q2_i , g_i ! Loop and q-points
|
|
|
|
integer :: l, m
|
|
real(dp) :: prefactor ! Final summation of sigma
|
|
|
|
integer :: i_proc, theta_i, i_grid, q_i, & !
|
|
ix, iy, iz ! Iterators
|
|
|
|
character(LEN=1) :: intvar
|
|
|
|
!real(dp) :: at_inverse(3,3)
|
|
|
|
allocate( d2y_dx2(Nqs, Nqs) )
|
|
allocate( u_vdW(dfftp%nnr, Nqs) )
|
|
|
|
sigma(:,:) = 0.0_DP
|
|
prefactor = 0.0_DP
|
|
|
|
!! --------------------------------------------------------------------------------------------------
|
|
!! Get u in k-space.
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
call thetas_to_uk(thetas, u_vdW)
|
|
|
|
!! --------------------------------------------------------------------------------------------------
|
|
!! Get u in real space.
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
call start_clock( 'vdW_ffts')
|
|
|
|
do theta_i = 1, Nqs
|
|
CALL invfft('Dense', u_vdW(:,theta_i), dfftp)
|
|
end do
|
|
|
|
call stop_clock( 'vdW_ffts')
|
|
|
|
!! --------------------------------------------------------------------------------------------------
|
|
!! Get the second derivatives for interpolating the P_i
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
call initialize_spline_interpolation(q_mesh, d2y_dx2(:,:))
|
|
|
|
!! ---------------------------------------------------------------------------------------------
|
|
|
|
i_grid = 0
|
|
|
|
!! ----------------------------------------------------------------------------------------------------
|
|
!! Do the real space integration to obtain the stress component
|
|
!! ----------------------------------------------------------------------------------------------------
|
|
|
|
|
|
do i_grid = 1, dfftp%nnr
|
|
|
|
q_low = 1
|
|
q_hi = Nqs
|
|
|
|
!
|
|
! Figure out which bin our value of q0 is in in the q_mesh
|
|
!
|
|
do while ( (q_hi - q_low) > 1)
|
|
|
|
q = int((q_hi + q_low)/2)
|
|
|
|
if (q_mesh(q) > q0(i_grid)) then
|
|
q_hi = q
|
|
else
|
|
q_low = q
|
|
end if
|
|
|
|
end do
|
|
|
|
if (q_hi == q_low) call errore('stress_vdW_gradient','qhi == qlow',1)
|
|
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
dq = q_mesh(q_hi) - q_mesh(q_low)
|
|
|
|
a = (q_mesh(q_hi) - q0(i_grid))/dq
|
|
b = (q0(i_grid) - q_mesh(q_low))/dq
|
|
c = (a**3 - a)*dq**2/6.0D0
|
|
d = (b**3 - b)*dq**2/6.0D0
|
|
e = (3.0D0*a**2 - 1.0D0)*dq/6.0D0
|
|
f = (3.0D0*b**2 - 1.0D0)*dq/6.0D0
|
|
|
|
do q_i = 1, Nqs
|
|
|
|
y(:) = 0.0D0
|
|
y(q_i) = 1.0D0
|
|
|
|
dP_dq0 = (y(q_hi) - y(q_low))/dq - e*d2y_dx2(q_i,q_low) + f*d2y_dx2(q_i,q_hi)
|
|
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
prefactor = u_vdW(i_grid,q_i) * dP_dq0 * dq0_dgradrho(i_grid)
|
|
|
|
do l = 1, 3
|
|
do m = 1, l
|
|
|
|
sigma (l, m) = sigma (l, m) - e2 * prefactor * &
|
|
(gradient_rho(i_grid,l) * gradient_rho(i_grid,m))
|
|
enddo
|
|
enddo
|
|
|
|
!! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
end do
|
|
|
|
end do
|
|
|
|
#ifdef __MPI
|
|
call mp_sum( sigma, intra_pool_comm )
|
|
#endif
|
|
|
|
call dscal (9, 1.d0 / (dfftp%nr1 * dfftp%nr2 * dfftp%nr3), sigma, 1)
|
|
|
|
deallocate( d2y_dx2, u_vdW )
|
|
|
|
END SUBROUTINE stress_vdW_DF_gradient
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | STRESS_VDW_DF_KERNEL |
|
|
!! | |
|
|
|
|
SUBROUTINE stress_vdW_DF_kernel (total_rho, q0, thetas, sigma)
|
|
|
|
!! Modules to include
|
|
!! ----------------------------------------------------------------------------------
|
|
use gvect, ONLY : ngm, nl, g, nl, gg, igtongl, gl, ngl, gstart
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : omega, tpiba, tpiba2
|
|
USE constants, ONLY: pi
|
|
|
|
implicit none
|
|
|
|
real(dp), intent(IN) :: q0(:)
|
|
real(dp), intent(IN) :: total_rho(:)
|
|
real(dp), intent(inout) :: sigma(3,3) !
|
|
complex(dp), intent(IN) :: thetas(:,:)
|
|
|
|
real(dp), allocatable :: dkernel_of_dk(:,:) !
|
|
|
|
integer :: l, m, q1_i, q2_i , g_i !
|
|
real(dp) :: g2, ngmod2, g_kernel, G_multiplier !
|
|
integer :: last_g, theta_i
|
|
|
|
allocate( dkernel_of_dk(Nqs, Nqs) )
|
|
|
|
sigma(:,:) = 0.0_DP
|
|
|
|
!! --------------------------------------------------------------------------------------------------
|
|
!! Integration in g-space
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
last_g = -1
|
|
|
|
G_multiplier = 1.0D0
|
|
if (gamma_only) G_multiplier = 2.0D0
|
|
|
|
do g_i = gstart, ngm
|
|
|
|
g2 = gg (g_i) * tpiba2
|
|
g_kernel = sqrt(g2)
|
|
|
|
if ( igtongl(g_i) .ne. last_g) then
|
|
|
|
call interpolate_Dkernel_Dk(g_kernel, dkernel_of_dk) ! Gets the derivatives
|
|
last_g = igtongl(g_i)
|
|
|
|
end if
|
|
|
|
do q2_i = 1, Nqs
|
|
do q1_i = 1, Nqs
|
|
do l = 1, 3
|
|
do m = 1, l
|
|
|
|
sigma (l, m) = sigma (l, m) - G_multiplier * 0.5 * e2 *&
|
|
thetas(nl(g_i),q1_i)*dkernel_of_dk(q1_i,q2_i)*conjg(thetas(nl(g_i),q2_i))* &
|
|
(g (l, g_i) * g (m, g_i) * tpiba2) / g_kernel
|
|
end do
|
|
end do
|
|
enddo
|
|
end do
|
|
|
|
if (g_i < gstart ) sigma(:,:) = sigma(:,:) / G_multiplier
|
|
|
|
enddo
|
|
|
|
#ifdef __MPI
|
|
call mp_sum( sigma, intra_pool_comm )
|
|
#endif
|
|
|
|
deallocate( dkernel_of_dk )
|
|
|
|
END SUBROUTINE stress_vdW_DF_kernel
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | GET_Q0_ON_GRID |
|
|
!! |__________________|
|
|
|
|
!! This routine first calculates the q value defined in (DION equations 11 and 12), then
|
|
!! saturates it according to (SOLER equation 7).
|
|
|
|
SUBROUTINE get_q0_on_grid (total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho)
|
|
!!
|
|
!! more specifically it calcultates the following
|
|
!!
|
|
!! q0(ir) = q0 as defined above
|
|
!! dq0_drho(ir) = total_rho * d q0 /d rho
|
|
!! dq0_dgradrho = total_rho / |gradient_rho| * d q0 / d |gradient_rho|
|
|
!!
|
|
|
|
USE fft_base, ONLY : dfftp
|
|
USE kernel_table, ONLY : q_cut, q_min
|
|
|
|
real(dp), intent(IN) :: total_rho(:), gradient_rho(:,:) !! Input variables needed
|
|
|
|
real(dp), intent(OUT) :: q0(:), dq0_drho(:), dq0_dgradrho(:) !! Output variables that have been allocated
|
|
! !! outside this routine but will be set here.
|
|
! _
|
|
real(dp), parameter :: LDA_A = 0.031091D0, LDA_a1 = 0.2137D0 !
|
|
real(dp), parameter :: LDA_b1 = 7.5957D0 , LDA_b2 = 3.5876D0 ! see J.P. Perdew and Yue Wang, Phys. Rev. B 45, 13244 (1992).
|
|
real(dp), parameter :: LDA_b3 = 1.6382D0 , LDA_b4 = 0.49294D0 !_
|
|
real(dp) :: Z_ab = -0.8491D0 !! see DION
|
|
|
|
integer, parameter :: m_cut = 12 !! How many terms to include in the sum
|
|
! !! of SOLER equation 7
|
|
|
|
|
|
real(dp) :: kF, r_s, sqrt_r_s, gradient_correction !! Intermediate variables needed to get q and q0
|
|
real(dp) :: LDA_1, LDA_2, q, exponent !!
|
|
|
|
real(dp) :: dq0_dq !! The derivative of the saturated q0 with respect to q.
|
|
! !! Needed by dq0_drho and dq0_dgradrho by the chain rule.
|
|
|
|
integer :: i_grid, index, count=0 !! Indexing variables
|
|
|
|
if (vdw_type==1) Z_ab = -0.8491D0
|
|
if (vdw_type==2) Z_ab = -1.887D0
|
|
|
|
|
|
! initialize q0-related arrays ...
|
|
q0(:) = q_cut
|
|
dq0_drho(:) = 0.0_DP
|
|
dq0_dgradrho(:) = 0.0_DP
|
|
|
|
do i_grid = 1, dfftp%nnr
|
|
|
|
!! This prevents numerical problems. If the charge density is negative (an
|
|
!! unphysical situation), we simply treat it as very small. In that case,
|
|
!! q0 will be very large and will be saturated. For a saturated q0 the derivative
|
|
!! dq0_dq will be 0 so we set q0 = q_cut and dq0_drho = dq0_dgradrho = 0 and go on
|
|
!! to the next point.
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
if (total_rho(i_grid) < epsr) cycle
|
|
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
!! Calculate some intermediate values needed to find q
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
kF = (3.0D0*pi*pi*total_rho(i_grid))**(1.0D0/3.0D0)
|
|
r_s = (3.0D0/(4.0D0*pi*total_rho(i_grid)))**(1.0D0/3.0D0)
|
|
sqrt_r_s = sqrt(r_s)
|
|
|
|
gradient_correction = -Z_ab/(36.0D0*kF*total_rho(i_grid)**2) &
|
|
* (gradient_rho(i_grid,1)**2+gradient_rho(i_grid,2)**2+gradient_rho(i_grid,3)**2)
|
|
|
|
LDA_1 = 8.0D0*pi/3.0D0*(LDA_A*(1.0D0+LDA_a1*r_s))
|
|
LDA_2 = 2.0D0*LDA_A * (LDA_b1*sqrt_r_s + LDA_b2*r_s + LDA_b3*r_s*sqrt_r_s + LDA_b4*r_s*r_s)
|
|
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
!! This is the q value defined in equations 11 and 12 of DION
|
|
!! ---------------------------------------------------------------
|
|
|
|
q = kF + LDA_1 * log(1.0D0+1.0D0/LDA_2) + gradient_correction
|
|
|
|
!! ---------------------------------------------------------------
|
|
|
|
!! Here, we calculate q0 by saturating q according to equation 7 of SOLER. Also, we find
|
|
!! the derivative dq0_dq needed for the derivatives dq0_drho and dq0_dgradrh0 discussed below.
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
exponent = 0.0D0
|
|
dq0_dq = 0.0D0
|
|
|
|
do index = 1, m_cut
|
|
|
|
exponent = exponent + ( (q/q_cut)**index)/index
|
|
dq0_dq = dq0_dq + ( (q/q_cut)**(index-1))
|
|
|
|
end do
|
|
|
|
q0(i_grid) = q_cut*(1.0D0 - exp(-exponent))
|
|
dq0_dq = dq0_dq * exp(-exponent)
|
|
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
!! This is to handle a case with q0 too small. We simply set it to the smallest q value in
|
|
!! out q_mesh. Hopefully this doesn't get used often (ever)
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
if (q0(i_grid) < q_min) then
|
|
|
|
q0(i_grid) = q_min
|
|
|
|
end if
|
|
|
|
!! ---------------------------------------------------------------------------------------
|
|
|
|
!! Here we find derivatives. These are actually the density times the derivative of q0 with respect
|
|
!! to rho and gradient_rho. The density factor comes in since we are really differentiating
|
|
!! theta = (rho)*P(q0) with respect to density (or its gradient) which will be
|
|
!! dtheta_drho = P(q0) + dP_dq0 * [rho * dq0_dq * dq_drho] and
|
|
!! dtheta_dgradient_rho = dP_dq0 * [rho * dq0_dq * dq_dgradient_rho]
|
|
!! The parts in square brackets are what is calculated here. The dP_dq0 term will be interpolated
|
|
!! later. There should actually be a factor of the magnitude of the gradient in the gradient_rho derivative
|
|
!! but that cancels out when we differentiate the magnitude of the gradient with respect to a particular
|
|
!! component.
|
|
!! -------------------------------------------------------------------------------------------------------------------------
|
|
|
|
dq0_drho(i_grid) = dq0_dq * (kF/3.0D0 - 7.0D0/3.0D0*gradient_correction &
|
|
- 8.0D0*pi/9.0D0 * LDA_A*LDA_a1*r_s*log(1.0D0+1.0D0/LDA_2) &
|
|
+ LDA_1/(LDA_2*(1.0D0 + LDA_2)) &
|
|
* (2.0D0*LDA_A*(LDA_b1/6.0D0*sqrt_r_s + LDA_b2/3.0D0*r_s + LDA_b3/2.0D0*r_s*sqrt_r_s + 2.0D0*LDA_b4/3.0D0*r_s**2)))
|
|
|
|
dq0_dgradrho(i_grid) = total_rho(i_grid) * dq0_dq * 2.0D0 * (-Z_ab)/(36.0D0*kF*total_rho(i_grid)**2)
|
|
|
|
!! --------------------------------------------------------------------------------------------------------------------------
|
|
|
|
end do
|
|
|
|
end SUBROUTINE get_q0_on_grid
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | GET_THETAS_ON_GRID |
|
|
!! |______________________|
|
|
|
|
|
|
SUBROUTINE get_thetas_on_grid (total_rho, q0_on_grid, thetas)
|
|
|
|
real(dp), intent(in) :: total_rho(:), q0_on_grid(:) !! Input arrays
|
|
|
|
complex(dp), intent(inout):: thetas(:,:) !! value of thetas for the grid points
|
|
! !! assigned to this processor. The format
|
|
! !! is thetas(grid_point, theta_i)
|
|
! NB: thetas are returned in reciprocal space
|
|
|
|
integer :: i_grid, Ngrid_points !! An index for the point on the grid and the total
|
|
! !! number of grid points
|
|
integer :: theta_i !! an index
|
|
|
|
Ngrid_points = size(q0_on_grid)
|
|
|
|
|
|
!! Interpolate the P_i polynomials defined in equation 3 in SOLER for the particular
|
|
!! q0 values we have.
|
|
CALL spline_interpolation(q_mesh, q0_on_grid, thetas)
|
|
|
|
!! Form the thetas where theta is defined as rho*p_i(q0)
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
do i_grid = 1, Ngrid_points
|
|
|
|
thetas(i_grid,:) = thetas(i_grid,:) * total_rho(i_grid)
|
|
|
|
end do
|
|
|
|
!! ------------------------------------------------------------------------------------
|
|
!! Get thetas in reciprocal space.
|
|
|
|
call start_clock( 'vdW_ffts')
|
|
|
|
do theta_i = 1, Nqs
|
|
|
|
CALL fwfft ('Dense', thetas(:,theta_i), dfftp)
|
|
end do
|
|
|
|
call stop_clock( 'vdW_ffts')
|
|
|
|
END SUBROUTINE get_thetas_on_grid
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | SPLINE_INTERPOLATION |
|
|
!! |________________________|
|
|
|
|
|
|
!! This routine is modeled after an algorithm from "Numerical Recipes in C" by Cambridge University
|
|
!! press, page 97. It was adapted for Fortran, of course and for the problem at hand, in that it finds
|
|
!! the bin a particular x value is in and then loops over all the P_i functions so we only have to find
|
|
!! the bin once.
|
|
|
|
|
|
SUBROUTINE spline_interpolation (x, evaluation_points, values)
|
|
|
|
|
|
real(dp), intent(in) :: x(:), evaluation_points(:) !! Input variables. The x values used to form the interpolation
|
|
! !! (q_mesh in this case) and the values of q0 for which we are
|
|
! !! interpolating the function
|
|
|
|
complex(dp), intent(inout) :: values(:,:) !! An output array (allocated outside this routine) that stores the
|
|
! !! interpolated values of the P_i (SOLER equation 3) polynomials. The
|
|
! !! format is values(grid_point, P_i)
|
|
|
|
integer :: Ngrid_points, Nx !! Total number of grid points to evaluate and input x points
|
|
|
|
real(dp), allocatable, save :: d2y_dx2(:,:) !! The second derivatives required to do the interpolation
|
|
|
|
integer :: i_grid, lower_bound, upper_bound, index, P_i !! Some indexing variables
|
|
|
|
real(dp), allocatable :: y(:) !! Temporary variables needed for the interpolation
|
|
real(dp) :: a, b, c, d, dx !!
|
|
|
|
Nx = size(x)
|
|
Ngrid_points = size(evaluation_points)
|
|
|
|
|
|
!! Allocate the temporary array
|
|
allocate( y(Nx) )
|
|
|
|
!! If this is the first time this routine has been called we need to get the second
|
|
!! derivatives (d2y_dx2) required to perform the interpolations. So we allocate the
|
|
!! array and call initialize_spline_interpolation to get d2y_dx2.
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
if (.not. allocated(d2y_dx2) ) then
|
|
|
|
allocate( d2y_dx2(Nx,Nx) )
|
|
call initialize_spline_interpolation(x, d2y_dx2)
|
|
|
|
end if
|
|
|
|
!! ------------------------------------------------------------------------------------
|
|
|
|
|
|
do i_grid=1, Ngrid_points
|
|
|
|
lower_bound = 1
|
|
upper_bound = Nx
|
|
|
|
do while ( (upper_bound - lower_bound) > 1 )
|
|
|
|
index = (upper_bound+lower_bound)/2
|
|
|
|
if ( evaluation_points(i_grid) > x(index) ) then
|
|
lower_bound = index
|
|
else
|
|
upper_bound = index
|
|
end if
|
|
|
|
end do
|
|
|
|
dx = x(upper_bound)-x(lower_bound)
|
|
|
|
a = (x(upper_bound) - evaluation_points(i_grid))/dx
|
|
b = (evaluation_points(i_grid) - x(lower_bound))/dx
|
|
c = ((a**3-a)*dx**2)/6.0D0
|
|
d = ((b**3-b)*dx**2)/6.0D0
|
|
|
|
do P_i = 1, Nx
|
|
|
|
y = 0
|
|
y(P_i) = 1
|
|
|
|
values(i_grid, P_i) = a*y(lower_bound) + b*y(upper_bound) &
|
|
+ (c*d2y_dx2(P_i,lower_bound) + d*d2y_dx2(P_i, upper_bound))
|
|
|
|
end do
|
|
|
|
end do
|
|
|
|
deallocate( y )
|
|
|
|
END SUBROUTINE spline_interpolation
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | INITIALIZE_SPLINE_INTERPOLATION |
|
|
!! |___________________________________|
|
|
|
|
|
|
!! This routine is modeled after an algorithm from "Numerical Recipes in C" by Cambridge
|
|
!! University Press, pages 96-97. It was adapted for Fortran and for the problem at hand.
|
|
|
|
SUBROUTINE initialize_spline_interpolation (x, d2y_dx2)
|
|
|
|
real(dp), intent(in) :: x(:) !! The input abscissa values
|
|
real(dp), intent(inout) :: d2y_dx2(:,:) !! The output array (allocated outside this routine)
|
|
! !! that holds the second derivatives required for
|
|
! !! interpolating the function
|
|
|
|
integer :: Nx, P_i, index !! The total number of x points and some indexing
|
|
! !! variables
|
|
|
|
real(dp), allocatable :: temp_array(:), y(:) !! Some temporary arrays required. y is the array
|
|
! !! that holds the funcion values (all either 0 or 1 here).
|
|
|
|
real(dp) :: temp1, temp2 !! Some temporary variables required
|
|
|
|
|
|
|
|
Nx = size(x)
|
|
|
|
allocate( temp_array(Nx), y(Nx) )
|
|
|
|
do P_i=1, Nx
|
|
|
|
|
|
!! In the Soler method, the polynomicals that are interpolated are Kroneker delta funcions
|
|
!! at a particular q point. So, we set all y values to 0 except the one corresponding to
|
|
!! the particular function P_i.
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
y = 0.0D0
|
|
y(P_i) = 1.0D0
|
|
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
d2y_dx2(P_i,1) = 0.0D0
|
|
temp_array(1) = 0.0D0
|
|
|
|
do index = 2, Nx-1
|
|
|
|
temp1 = (x(index)-x(index-1))/(x(index+1)-x(index-1))
|
|
temp2 = temp1 * d2y_dx2(P_i,index-1) + 2.0D0
|
|
d2y_dx2(P_i,index) = (temp1-1.0D0)/temp2
|
|
temp_array(index) = (y(index+1)-y(index))/(x(index+1)-x(index)) &
|
|
- (y(index)-y(index-1))/(x(index)-x(index-1))
|
|
temp_array(index) = (6.0D0*temp_array(index)/(x(index+1)-x(index-1)) &
|
|
- temp1*temp_array(index-1))/temp2
|
|
|
|
end do
|
|
|
|
d2y_dx2(P_i,Nx) = 0.0D0
|
|
|
|
do index=Nx-1, 1, -1
|
|
|
|
d2y_dx2(P_i,index) = d2y_dx2(P_i,index) * d2y_dx2(P_i,index+1) + temp_array(index)
|
|
|
|
end do
|
|
|
|
end do
|
|
|
|
deallocate( temp_array, y)
|
|
|
|
end SUBROUTINE initialize_spline_interpolation
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | INTERPOLATE_KERNEL |
|
|
!! |____________________|
|
|
|
|
|
|
!! This routine is modeled after an algorithm from "Numerical Recipes in C" by Cambridge
|
|
!! University Press, page 97. Adapted for Fortran and the problem at hand. This function is used to
|
|
!! find the Phi_alpha_beta needed for equations 11 and 14 of SOLER.
|
|
|
|
|
|
subroutine interpolate_kernel(k, kernel_of_k)
|
|
|
|
USE kernel_table, ONLY : r_max, Nr_points, kernel, d2phi_dk2, dk
|
|
|
|
real(dp), intent(in) :: k !! Input value, the magnitude of the g-vector for the
|
|
! !! current point.
|
|
|
|
real(dp), intent(inout) :: kernel_of_k(:,:) !! An output array (allocated outside this routine)
|
|
! !! that holds the interpolated value of the kernel
|
|
! !! for each pair of q points (i.e. the phi_alpha_beta
|
|
! !! of the Soler method.
|
|
|
|
integer :: q1_i, q2_i, k_i !! Indexing variables
|
|
|
|
real(dp) :: A, B, C, D !! Intermediate values for the interpolation
|
|
|
|
|
|
!! Check to make sure that the kernel table we have is capable of dealing with this
|
|
!! value of k. If k is larger than Nr_points*2*pi/r_max then we can't perform the
|
|
!! interpolation. In that case, a kernel file should be generated with a larger number
|
|
!! of radial points.
|
|
!! -------------------------------------------------------------------------------------
|
|
|
|
if ( k >= Nr_points*dk ) then
|
|
|
|
write(*,'(A,F10.5,A,F10.5)') "k = ", k, " k_max = ",Nr_points*dk
|
|
call errore('interpolate kernel', 'k value requested is out of range',1)
|
|
|
|
end if
|
|
|
|
!! -------------------------------------------------------------------------------------
|
|
|
|
kernel_of_k = 0.0D0
|
|
|
|
!! This integer division figures out which bin k is in since the kernel
|
|
!! is set on a uniform grid.
|
|
k_i = int(k/dk)
|
|
|
|
!! Test to see if we are trying to interpolate a k that is one of the actual
|
|
!! function points we have. The value is just the value of the function in that
|
|
!! case.
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
if (mod(k,dk) == 0) then
|
|
|
|
do q1_i = 1, Nqs
|
|
do q2_i = 1, q1_i
|
|
|
|
kernel_of_k(q1_i, q2_i) = kernel(k_i,q1_i, q2_i)
|
|
kernel_of_k(q2_i, q1_i) = kernel(k_i,q2_i, q1_i)
|
|
|
|
end do
|
|
end do
|
|
|
|
return
|
|
|
|
end if
|
|
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
|
|
!! If we are not on a function point then we carry out the interpolation
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
A = (dk*(k_i+1.0D0) - k)/dk
|
|
B = (k - dk*k_i)/dk
|
|
C = (A**3-A)*dk**2/6.0D0
|
|
D = (B**3-B)*dk**2/6.0D0
|
|
|
|
do q1_i = 1, Nqs
|
|
do q2_i = 1, q1_i
|
|
|
|
kernel_of_k(q1_i, q2_i) = A*kernel(k_i, q1_i, q2_i) + B*kernel(k_i+1, q1_i, q2_i) &
|
|
+(C*d2phi_dk2(k_i, q1_i, q2_i) + D*d2phi_dk2(k_i+1, q1_i, q2_i))
|
|
|
|
kernel_of_k(q2_i, q1_i) = kernel_of_k(q1_i, q2_i)
|
|
|
|
end do
|
|
end do
|
|
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
|
|
end subroutine interpolate_kernel
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | INTERPOLATE_DKERNEL_DK |
|
|
!! |________________________|
|
|
|
|
|
|
|
|
subroutine interpolate_Dkernel_Dk(k, dkernel_of_dk)
|
|
|
|
USE kernel_table, ONLY : r_max, Nr_points, kernel, d2phi_dk2, dk
|
|
|
|
implicit none
|
|
|
|
real(dp), intent(in) :: k !! Input value, the magnitude of the g-vector for the
|
|
! !! current point.
|
|
|
|
real(dp), intent(inout) :: dkernel_of_dk(Nqs,Nqs) !! An output array (allocated outside this routine)
|
|
! !! that holds the interpolated value of the kernel
|
|
! !! for each pair of q points (i.e. the phi_alpha_beta
|
|
! !! of the Soler method.
|
|
|
|
integer :: q1_i, q2_i, k_i !! Indexing variables
|
|
|
|
real(dp) :: A, B, dAdk, dBdk, dCdk, dDdk !! Intermediate values for the interpolation
|
|
|
|
|
|
!! -------------------------------------------------------------------------------------
|
|
|
|
if ( k >= Nr_points*dk ) then
|
|
|
|
write(*,'(A,F10.5,A,F10.5)') "k = ", k, " k_max = ",Nr_points*dk
|
|
call errore('interpolate kernel', 'k value requested is out of range',1)
|
|
|
|
end if
|
|
|
|
!! -------------------------------------------------------------------------------------
|
|
|
|
dkernel_of_dk = 0.0D0
|
|
|
|
k_i = int(k/dk)
|
|
|
|
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
A = (dk*(k_i+1.0D0) - k)/dk
|
|
B = (k - dk*k_i)/dk
|
|
|
|
dAdk = -1.0D0/dk
|
|
dBdk = 1.0D0/dk
|
|
dCdk = -((3*A**2 -1.0D0)/6.0D0)*dk
|
|
dDdk = ((3*B**2 -1.0D0)/6.0D0)*dk
|
|
|
|
do q1_i = 1, Nqs
|
|
do q2_i = 1, q1_i
|
|
|
|
dkernel_of_dk(q1_i, q2_i) = dAdk*kernel(k_i, q1_i, q2_i) + dBdk*kernel(k_i+1, q1_i, q2_i) &
|
|
+ dCdk*d2phi_dk2(k_i, q1_i, q2_i) + dDdk*d2phi_dk2(k_i+1, q1_i, q2_i)
|
|
|
|
dkernel_of_dk(q2_i, q1_i) = dkernel_of_dk(q1_i, q2_i)
|
|
|
|
end do
|
|
end do
|
|
|
|
!! ----------------------------------------------------------------------------------------
|
|
|
|
|
|
end subroutine interpolate_Dkernel_Dk
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | NUMERICAL_GRADIENT |
|
|
!! |_______________________|
|
|
|
|
|
|
#ifdef FFTGRADIENT
|
|
!! Calculates the gradient of the charge density numerically on the grid. We use
|
|
!! the PWSCF gradient style.
|
|
|
|
subroutine numerical_gradient(total_rho, gradient_rho)
|
|
|
|
use gvect, ONLY : ngm, nl, g, nlm
|
|
USE cell_base, ONLY : tpiba
|
|
USE fft_base, ONLY : dfftp
|
|
USE fft_interfaces, ONLY : fwfft, invfft
|
|
!
|
|
! I/O variables
|
|
!
|
|
real(dp), intent(in) :: total_rho(:) !! Input array holding total charge density.
|
|
|
|
real(dp), intent(out) :: gradient_rho(:,:) !! Output array that will holds the gradient
|
|
! !! of the charge density.
|
|
! local variables
|
|
!
|
|
integer :: icar !! counter on cartesian components
|
|
complex(dp), allocatable :: c_rho(:) !! auxiliary complex array for rho
|
|
complex(dp), allocatable :: c_grho(:) !! auxiliary complex array for grad rho
|
|
|
|
! rho in G space
|
|
allocate ( c_rho(dfftp%nnr), c_grho(dfftp%nnr) )
|
|
c_rho(1:dfftp%nnr) = CMPLX(total_rho(1:dfftp%nnr),0.0_DP)
|
|
CALL fwfft ('Dense', c_rho, dfftp)
|
|
|
|
do icar=1,3
|
|
! compute gradient in G space
|
|
c_grho(:) =CMPLX(0.0_DP,0.0_DP)
|
|
c_grho(nl(:)) = CMPLX (0.0_DP,1.0_DP) * tpiba * g(icar,:) * c_rho(nl(:))
|
|
if (gamma_only) c_grho( nlm(:) ) = CONJG( c_grho( nl(:) ) )
|
|
|
|
! back in real space
|
|
CALL invfft ('Dense', c_grho, dfftp)
|
|
gradient_rho(:,icar) = REAL( c_grho(:) )
|
|
end do
|
|
deallocate ( c_rho, c_grho )
|
|
|
|
return
|
|
|
|
end subroutine numerical_gradient
|
|
|
|
|
|
#else
|
|
!! Calculates the gradient of the charge density numerically on the grid. We could simply
|
|
!! use the PWSCF gradient routine but we need the derivative of the gradient at point j
|
|
!! with respect to the density at point i for the potential (SOLER equation 13). This is
|
|
!! difficult to do with the standard means of calculating the density gradient but trivial
|
|
!! in the case of the numerical formula becuase the derivative of the gradient at point j
|
|
!! with respect to the density at point i is just whatever the coefficient is in the numerical
|
|
!! derivative formula.
|
|
|
|
|
|
subroutine numerical_gradient(full_rho, Nneighbors, gradient_rho, my_start_z, my_end_z)
|
|
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : alat, at
|
|
|
|
real(dp), intent(in) :: full_rho(:) !! Input array holding the value of the total charge density
|
|
! !! on all grid points of the simulation cell
|
|
|
|
integer, intent(in) :: Nneighbors, my_start_z, my_end_z !! Input variables giving the order of the numerical derivative,
|
|
! !! and the starting and ending z-slabs for the given processor.
|
|
|
|
real(dp), intent(inout) :: gradient_rho(:,:) !! Output array (allocated outside the routine) that holds the
|
|
! !! gradient of the charge density only in the region assigned to
|
|
! !! the given processor in the format:
|
|
! !! gradient_rho(grid_point, cartesian_component)
|
|
|
|
real(dp), pointer, save :: coefficients(:) !! A pointer to an array of coefficients used for the numerical
|
|
! !! differentiation. See gradient_coefficients function for more
|
|
! !! detail.
|
|
|
|
integer, pointer, save :: indices3d(:,:,:) !! A pointer to a rank 3 array that gives the relation between the
|
|
! !! x, y, and z indices of a point and its index in the charge density
|
|
! !! array. Used to easily find neighbors in the x, y, and z directions.
|
|
|
|
integer :: i_grid, ix1, ix2, ix3, nx !! Indexing variables
|
|
|
|
real(dp) :: temp(3) !! A temporary array for the gradient at a point
|
|
|
|
real(dp), save :: at_inverse(3,3) !! The inverse of the matrix of unit cell basis vectors
|
|
|
|
logical, save :: have_at_inverse = .false. !! Flag to determine if we have found the inverse matrix yet
|
|
|
|
|
|
gradient_rho = 0.0D0
|
|
|
|
!! Get pointers to the gradient coefficients and the 3d index array needed to find
|
|
!! the gradient if we don't have them already.
|
|
!! ----------------------------------------------------------------------------------
|
|
|
|
if (.not. associated(indices3d) ) then
|
|
|
|
indices3d => get_3d_indices(Nneighbors)
|
|
coefficients => gradient_coefficients(Nneighbors)
|
|
|
|
end if
|
|
|
|
!! ----------------------------------------------------------------------------------
|
|
|
|
!! Here we need to get the transformation matrix that takes our calculated "gradient"
|
|
!! , gradient_rho()!! to the real thing. It is just the (normalized) inverse of the matrix of unit cell
|
|
!! basis vectors. If the unit cell has orthogonal basis vectors then this will be a
|
|
!! diagonal matrix with the diagonal elements bein 1/(basis vector length). In the
|
|
!! general case this will not be diagonal (e.g. for hexagonal unit cells).
|
|
!! ----------------------------------------------------------------------------------
|
|
|
|
if (.not. have_at_inverse) then
|
|
|
|
at_inverse = alat*at
|
|
call invert_3x3_matrix(at_inverse)
|
|
|
|
! Normalize by the number of grid points in each direction
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
at_inverse(1,:) = at_inverse(1,:) * dble(dfftp%nr1x)
|
|
at_inverse(2,:) = at_inverse(2,:) * dble(dfftp%nr2x)
|
|
at_inverse(3,:) = at_inverse(3,:) * dble(dfftp%nr3x)
|
|
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
! Take the transpose because of the way Fortran does matmul() (used below)
|
|
at_inverse = transpose(at_inverse)
|
|
|
|
! Mark that we have gotten the transformation matrix so we don't have to find it again
|
|
have_at_inverse = .true.
|
|
|
|
end if
|
|
|
|
!! ----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
i_grid = 0
|
|
|
|
!! Here we loop over all of the points assigned to a given processor. For each point we loop
|
|
!! over all relavant neighbors (determined by the variable Nneighbors) and multiply the value
|
|
!! of the density of each by the corresponding coefficient. We then tranform the vector by
|
|
!! multiplying it by the inverse of the unit cell matrix found above. This takes care of cases
|
|
!! where the basis vectors are not the same length or are not even orthogonal.
|
|
!! -----------------------------------------------------------------------------------------------
|
|
|
|
do ix3 = my_start_z, my_end_z
|
|
do ix2 = 1, dfftp%nr2x
|
|
do ix1 = 1, dfftp%nr1x
|
|
|
|
i_grid = i_grid + 1
|
|
|
|
temp = 0.0D0
|
|
|
|
do nx = -Nneighbors, Nneighbors
|
|
|
|
temp(1) = temp(1) + coefficients(nx) * full_rho(indices3d(ix1+nx,ix2,ix3))
|
|
temp(2) = temp(2) + coefficients(nx) * full_rho(indices3d(ix1,ix2+nx,ix3))
|
|
temp(3) = temp(3) + coefficients(nx) * full_rho(indices3d(ix1,ix2,ix3+nx))
|
|
|
|
end do
|
|
|
|
gradient_rho(i_grid,:) = matmul(at_inverse,temp)
|
|
|
|
end do
|
|
end do
|
|
end do
|
|
|
|
!! -----------------------------------------------------------------------------------------------
|
|
!! FAKE PATCH !!
|
|
!gradient_rho = 0.0D0
|
|
|
|
end subroutine numerical_gradient
|
|
|
|
#endif
|
|
|
|
!! #################################################################################################
|
|
!! | |
|
|
!! | thetas_to_uk |
|
|
!! |______________|
|
|
|
|
|
|
subroutine thetas_to_uk(thetas, u_vdW)
|
|
|
|
USE gvect, ONLY : nl, nlm, gg, ngm, igtongl, gl, ngl, gstart
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : tpiba, omega
|
|
|
|
complex(dp), intent(in) :: thetas(:,:) !! On input this variable holds the theta functions (equation 11, SOLER)
|
|
! !! in the format thetas(grid_point, theta_i).
|
|
complex(dp), intent(out) :: u_vdW(:,:)
|
|
! !! On output this array holds u_alpha(k) = Sum_j[theta_beta(k)phi_alpha_beta(k)]
|
|
|
|
real(dp), allocatable :: kernel_of_k(:,:) !! This array will hold the interpolated kernel values for each pair of q values
|
|
! !! in the q_mesh.
|
|
|
|
real(dp) :: g
|
|
integer :: last_g, g_i, q1_i, q2_i, count, i_grid !! Index variables
|
|
|
|
complex(dp) :: theta(Nqs) !! Temporary storage vector used since we are overwriting the thetas array here.
|
|
|
|
!! -------------------------------------------------------------------------------------------------
|
|
|
|
allocate( kernel_of_k(Nqs, Nqs) )
|
|
|
|
u_vdW(:,:) = CMPLX(0.0_DP,0.0_DP)
|
|
|
|
last_g = -1
|
|
|
|
do g_i = 1, ngm
|
|
|
|
if ( igtongl(g_i) .ne. last_g) then
|
|
|
|
g = sqrt(gl(igtongl(g_i))) * tpiba
|
|
call interpolate_kernel(g, kernel_of_k)
|
|
last_g = igtongl(g_i)
|
|
|
|
end if
|
|
|
|
theta = thetas(nl(g_i),:)
|
|
|
|
do q2_i = 1, Nqs
|
|
do q1_i = 1, Nqs
|
|
u_vdW(nl(g_i),q2_i) = u_vdW(nl(g_i),q2_i) + kernel_of_k(q2_i,q1_i)*theta(q1_i)
|
|
end do
|
|
end do
|
|
|
|
end do
|
|
|
|
if (gamma_only) u_vdW(nlm(:),:) = CONJG(u_vdW(nl(:),:))
|
|
|
|
deallocate( kernel_of_k )
|
|
|
|
!! -----------------------------------------------------------------------------------------------
|
|
|
|
end subroutine thetas_to_uk
|
|
|
|
!! #################################################################################################
|
|
!! | |
|
|
!! | VDW_ENERGY |
|
|
!! |_____________|
|
|
|
|
!! This routine carries out the integration of equation 11 of SOLER. It returns the non-local
|
|
!! exchange-correlation energy and the u_alpha(k) arrays used to find the u_alpha(r) arrays via
|
|
!! equations 14 and 15 in SOLER.
|
|
|
|
subroutine vdW_energy(thetas, vdW_xc_energy)
|
|
|
|
USE gvect, ONLY : nl, nlm, gg, ngm, igtongl, gl, ngl, gstart
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : tpiba, omega
|
|
|
|
complex(dp), intent(inout) :: thetas(:,:) !! On input this variable holds the theta functions
|
|
! !! (equation 11, SOLER) in the format thetas(grid_point, theta_i).
|
|
! !! On output this array holds
|
|
! !! u_alpha(k) = Sum_j[theta_beta(k)phi_alpha_beta(k)]
|
|
|
|
real(dp), intent(out) :: vdW_xc_energy !! The non-local correlation energy. An output variable.
|
|
|
|
real(dp), allocatable :: kernel_of_k(:,:) !! This array will hold the interpolated kernel values
|
|
! !! for each pair of q values in the q_mesh.
|
|
|
|
real(dp) :: g !! The magnitude of the current g vector
|
|
integer :: last_g !! The shell number of the last g vector
|
|
!
|
|
|
|
integer :: g_i, q1_i, q2_i, count, i_grid !! Index variables
|
|
|
|
complex(dp) :: theta(Nqs), thetam(Nqs), theta_g(Nqs) !! Temporary storage vector used since we are overwriting the thetas array here.
|
|
real(dp) :: G0_term, G_multiplier
|
|
|
|
complex(dp), allocatable :: u_vdw(:,:) !! temporary array holding u_alpha(k)
|
|
|
|
vdW_xc_energy = 0.0D0
|
|
|
|
allocate (u_vdW(dfftp%nnr,Nqs))
|
|
u_vdW(:,:) = CMPLX(0.0_DP,0.0_DP)
|
|
|
|
allocate( kernel_of_k(Nqs, Nqs) )
|
|
|
|
!! Loop over PWSCF's array of magnitude-sorted g-vector shells. For each shell, interpolate
|
|
!! the kernel at this magnitude of g, then find all points on the shell and carry out the
|
|
!! integration over those points. The PWSCF variables used here are
|
|
!! ngm = number of g-vectors on this processor, nl = an array that gives the indices into the
|
|
!! FFT grid for a particular g vector, igtongl = an array that gives the index of which shell a
|
|
!! particular g vector is in, gl = an array that gives the magnitude of the g vectors for each shell.
|
|
!! In essence, we are forming the reciprocal-space u(k) functions of SOLER equation 14. These are
|
|
!! kept in thetas array.
|
|
!! -------------------------------------------------------------------------------------------------
|
|
|
|
!!
|
|
!! Here we should use gstart,ngm but all the cases are handeld by conditionals inside the loop
|
|
!!
|
|
G_multiplier = 1.0D0
|
|
if (gamma_only) G_multiplier = 2.0D0
|
|
|
|
last_g = -1
|
|
|
|
do g_i = 1, ngm
|
|
|
|
if ( igtongl(g_i) .ne. last_g) then
|
|
|
|
g = sqrt(gl(igtongl(g_i))) * tpiba
|
|
call interpolate_kernel(g, kernel_of_k)
|
|
last_g = igtongl(g_i)
|
|
|
|
end if
|
|
|
|
theta = thetas(nl(g_i),:)
|
|
|
|
do q2_i = 1, Nqs
|
|
do q1_i = 1, Nqs
|
|
u_vdW(nl(g_i),q2_i) = u_vdW(nl(g_i),q2_i) + kernel_of_k(q2_i,q1_i)*theta(q1_i)
|
|
end do
|
|
vdW_xc_energy = vdW_xc_energy + G_multiplier * (u_vdW(nl(g_i),q2_i)*conjg(theta(q2_i)))
|
|
end do
|
|
|
|
if (g_i < gstart ) vdW_xc_energy = vdW_xc_energy / G_multiplier
|
|
|
|
end do
|
|
|
|
if (gamma_only) u_vdW(nlm(:),:) = CONJG(u_vdW(nl(:),:))
|
|
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
!! Apply scaling factors. The e2 comes from PWSCF's choice of units. This should be
|
|
!! 0.5 * e2 * vdW_xc_energy * (2pi)^3/omega * (omega)^2, with the (2pi)^3/omega being
|
|
!! the volume element for the integral (the volume of the reciprocal unit cell) and the
|
|
!! 2 factors of omega beging used to cancel the factor of 1/omega PWSCF puts on forward
|
|
!! FFTs of the 2 theta factors. 1 omega cancels and the (2pi)^3 cancels because there should
|
|
!! be a factor of 1/(2pi)^3 on the radial Fourier transform of phi that was left out to cancel
|
|
!! with this factor.
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
vdW_xc_energy = 0.5D0 * e2 * omega * vdW_xc_energy
|
|
|
|
deallocate( kernel_of_k )
|
|
thetas(:,:) = u_vdW(:,:)
|
|
deallocate (u_vdW)
|
|
!! ---------------------------------------------------------------------------------------------------
|
|
|
|
end subroutine vdW_energy
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | dv_drho_vdw |
|
|
!! |_________________|
|
|
|
|
#ifdef FFTGRADIENT
|
|
|
|
subroutine dv_drho_vdw(rho_valence, rho_core, drho, nspin, dv_drho)
|
|
|
|
USE gvect, ONLY : nl, g, nlm, ngm
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : alat, tpiba, omega
|
|
USE fft_scalar, ONLY : cfft3d
|
|
|
|
integer :: nspin
|
|
real(dp), intent(IN) :: rho_valence(:,:) !
|
|
real(dp), intent(IN) :: rho_core(:)
|
|
complex(DP), intent(IN) :: drho (dfftp%nnr, nspin)
|
|
complex(DP), intent(INOUT) :: dv_drho(dfftp%nnr, nspin)
|
|
|
|
!! -------------------------------------------------------------------------
|
|
!! For the potential
|
|
!! -------------------------------------------------------------------------
|
|
integer :: i_grid, theta_i, i_proc, I
|
|
real(dp) :: grid_cell_volume
|
|
real(dp), allocatable :: q0(:)
|
|
real(dp), allocatable :: gradient_rho(:,:)
|
|
real(dp), allocatable :: potential(:)
|
|
real(dp), allocatable :: dq0_drho(:)
|
|
real(dp), allocatable :: dq0_dgradrho(:)
|
|
complex(dp), allocatable :: thetas(:,:)
|
|
real(dp), allocatable :: total_rho(:)
|
|
complex(dp), allocatable :: u_vdW(:,:)
|
|
|
|
!! -------------------------------------------------------------------------
|
|
!! For the derivative
|
|
!! -------------------------------------------------------------------------
|
|
real(dp), allocatable :: potential_plus(:), potential_minus(:)
|
|
real(dp) :: lambda
|
|
real(DP), allocatable :: drho_real(:)
|
|
|
|
|
|
allocate( q0(dfftp%nnr) )
|
|
allocate( gradient_rho(dfftp%nnr, 3) )
|
|
allocate( dq0_drho(dfftp%nnr), dq0_dgradrho(dfftp%nnr) )
|
|
allocate( total_rho(dfftp%nnr) )
|
|
allocate( drho_real(dfftp%nnr) )
|
|
allocate( thetas(dfftp%nnr, Nqs) )
|
|
allocate( u_vdW(dfftp%nnr, Nqs) )
|
|
allocate( potential_plus(dfftp%nnr), potential_minus(dfftp%nnr) )
|
|
|
|
|
|
!! Derivative parameter
|
|
lambda = 0.01D0
|
|
|
|
|
|
!! Delta rho in real space
|
|
CALL invfft ('Dense', drho(:,1), dfftp)
|
|
drho_real(:) = REAL( drho(:,1) )
|
|
|
|
|
|
!! -------------------------------------------------------------------------
|
|
!! Potential plus
|
|
!! -------------------------------------------------------------------------
|
|
|
|
total_rho = rho_valence(:,1) + rho_core(:) + lambda*drho_real(:)
|
|
|
|
call numerical_gradient(total_rho,gradient_rho)
|
|
CALL get_q0_on_grid(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho)
|
|
CALL get_thetas_on_grid(total_rho, q0, thetas)
|
|
|
|
!!call vdW_energy(thetas, Ec_nl)
|
|
call thetas_to_uk(thetas, u_vdW)
|
|
|
|
call start_clock( 'vdW_ffts')
|
|
do theta_i = 1, Nqs
|
|
|
|
CALL invfft('Dense', u_vdW(:,theta_i), dfftp)
|
|
end do
|
|
call stop_clock( 'vdW_ffts')
|
|
|
|
!!call get_potential(q0, dq0_drho, dq0_dgradrho, gradient_rho, thetas, potential)
|
|
call get_potential(q0, dq0_drho, dq0_dgradrho, gradient_rho, u_vdW, potential_plus)
|
|
|
|
|
|
!! -------------------------------------------------------------------------
|
|
!! Potential minus
|
|
!! -------------------------------------------------------------------------
|
|
|
|
total_rho = rho_valence(:,1) + rho_core(:) - lambda*drho_real(:)
|
|
|
|
call numerical_gradient(total_rho,gradient_rho)
|
|
CALL get_q0_on_grid(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho)
|
|
CALL get_thetas_on_grid(total_rho, q0, thetas)
|
|
|
|
!!call vdW_energy(thetas, Ec_nl)
|
|
call thetas_to_uk(thetas, u_vdW)
|
|
|
|
call start_clock( 'vdW_ffts')
|
|
do theta_i = 1, Nqs
|
|
|
|
CALL invfft('Dense', u_vdW(:,theta_i), dfftp)
|
|
end do
|
|
call stop_clock( 'vdW_ffts')
|
|
|
|
!!call get_potential(q0, dq0_drho, dq0_dgradrho, gradient_rho, thetas, potential)
|
|
call get_potential(q0, dq0_drho, dq0_dgradrho, gradient_rho, u_vdW, potential_minus)
|
|
|
|
|
|
!! -------------------------------------------------------------------------
|
|
!! Derivative
|
|
!! -------------------------------------------------------------------------
|
|
|
|
dv_drho(:,1) = (potential_plus(:) - potential_minus(:))/(2*lambda)
|
|
|
|
|
|
!! -------------------------------------------------------------------------
|
|
!! Deallocate
|
|
!! -------------------------------------------------------------------------
|
|
|
|
CALL fwfft ('Dense', drho(:,1), dfftp)
|
|
|
|
deallocate( q0, gradient_rho, dq0_drho, dq0_dgradrho, total_rho)
|
|
deallocate( drho_real,thetas, u_vdW)
|
|
deallocate( potential_plus, potential_minus )
|
|
|
|
|
|
end subroutine dv_drho_vdw
|
|
|
|
|
|
#endif
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | GET_POTENTIAL |
|
|
!! |_________________|
|
|
|
|
!! This routine finds the non-local correlation contribution to the potential (i.e. the derivative of the non-local
|
|
!! piece of the energy with respect to density) given in SOLER equation 13. The u_alpha(k) functions were found
|
|
!! while calculating the energy. They are passed in as the matrix u_vdW. Most of the required derivatives were
|
|
!! calculated in the "get_q0_on_grid" routine, but the derivative of the interpolation polynomials, P_alpha(q),
|
|
!! (SOLER equation 3) with respect to q is interpolated here, along with the polynomials themselves.
|
|
|
|
#ifdef FFTGRADIENT
|
|
|
|
subroutine get_potential(q0, dq0_drho, dq0_dgradrho, gradient_rho, u_vdW, potential)
|
|
|
|
use gvect, ONLY : nl, g, nlm
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : alat, tpiba
|
|
|
|
real(dp), intent(in) :: q0(:), gradient_rho(:,:) !! Input arrays holding the value of q0 for all points assigned
|
|
! !! to this processor and the gradient of the charge density for
|
|
! !! points assigned to this processor.
|
|
|
|
real(dp), intent(in) :: dq0_drho(:), dq0_dgradrho(:)!! The derivative of q0 with respect to the charge density and
|
|
! !! gradient of the charge density (almost). See comments in
|
|
! !! the get_q0_on_grid subroutine above.
|
|
|
|
complex(dp), intent(in) :: u_vdW(:,:) !! The functions u_alpha(r) obtained by inverse transforming the
|
|
! !! functions u_alph(k). See equations 14 and 15 in SOLER
|
|
|
|
real(dp), intent(inout) :: potential(:) !! The non-local correlation potential for points on the grid over
|
|
! !! the whole cell (not just those assigned to this processor).
|
|
|
|
real(dp), allocatable, save :: d2y_dx2(:,:) !! Second derivatives of P_alpha polynomials for interpolation
|
|
|
|
integer :: i_grid, P_i,icar !! Index variables
|
|
|
|
integer :: q_low, q_hi, q !! Variables to find the bin in the q_mesh that a particular q0
|
|
! !! belongs to (for interpolation).
|
|
real(dp) :: dq, a, b, c, d, e, f !! Inermediate variables used in the interpolation of the polynomials
|
|
|
|
real(dp) :: y(Nqs), dP_dq0, P !! The y values for a given polynomial (all 0 exept for element i of P_i)
|
|
! !! The derivative of P at a given q0 and the value of P at a given q0. Both
|
|
! !! of these are interpolated below
|
|
|
|
real(dp), allocatable ::h_prefactor(:)
|
|
complex(dp), allocatable ::h(:)
|
|
|
|
allocate (h_prefactor(dfftp%nnr),h(dfftp%nnr))
|
|
|
|
potential = 0.0D0
|
|
h_prefactor = 0.0D0
|
|
|
|
!! -------------------------------------------------------------------------------------------
|
|
|
|
!! Get the second derivatives of the P_i functions for interpolation. We have already calculated
|
|
!! this once but it is very fast and it's just as easy to calculate it again.
|
|
!! ---------------------------------------------------------------------------------------------
|
|
|
|
if (.not. allocated( d2y_dx2) ) then
|
|
|
|
allocate( d2y_dx2(Nqs, Nqs) )
|
|
|
|
call initialize_spline_interpolation(q_mesh, d2y_dx2(:,:))
|
|
|
|
end if
|
|
|
|
!! ---------------------------------------------------------------------------------------------
|
|
|
|
|
|
do i_grid = 1,dfftp%nnr
|
|
|
|
q_low = 1
|
|
q_hi = Nqs
|
|
|
|
! Figure out which bin our value of q0 is in in the q_mesh
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
do while ( (q_hi - q_low) > 1)
|
|
|
|
q = int((q_hi + q_low)/2)
|
|
|
|
if (q_mesh(q) > q0(i_grid)) then
|
|
q_hi = q
|
|
else
|
|
q_low = q
|
|
end if
|
|
|
|
end do
|
|
|
|
if (q_hi == q_low) call errore('get_potential','qhi == qlow',1)
|
|
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
dq = q_mesh(q_hi) - q_mesh(q_low)
|
|
|
|
a = (q_mesh(q_hi) - q0(i_grid))/dq
|
|
b = (q0(i_grid) - q_mesh(q_low))/dq
|
|
c = (a**3 - a)*dq**2/6.0D0
|
|
d = (b**3 - b)*dq**2/6.0D0
|
|
e = (3.0D0*a**2 - 1.0D0)*dq/6.0D0
|
|
f = (3.0D0*b**2 - 1.0D0)*dq/6.0D0
|
|
|
|
do P_i = 1, Nqs
|
|
y = 0.0D0
|
|
y(P_i) = 1.0D0
|
|
|
|
dP_dq0 = (y(q_hi) - y(q_low))/dq - e*d2y_dx2(P_i,q_low) + f*d2y_dx2(P_i,q_hi)
|
|
|
|
P = a*y(q_low) + b*y(q_hi) + c*d2y_dx2(P_i,q_low) + d*d2y_dx2(P_i,q_hi)
|
|
|
|
!! The first term in equation 13 of SOLER
|
|
potential(i_grid) = potential(i_grid) + u_vdW(i_grid,P_i)* (P + dP_dq0 * dq0_drho(i_grid))
|
|
if (q0(i_grid) .ne. q_mesh(Nqs)) then
|
|
h_prefactor(i_grid) = h_prefactor(i_grid) + u_vdW(i_grid,P_i)* dP_dq0 * dq0_dgradrho(i_grid)
|
|
end if
|
|
end do
|
|
end do
|
|
do icar = 1,3
|
|
h(:) = CMPLX(h_prefactor(:) * gradient_rho(:,icar),0.0_DP)
|
|
CALL fwfft ('Dense', h, dfftp)
|
|
h(nl(:)) = CMPLX(0.0_DP,1.0_DP) * tpiba * g(icar,:) * h(nl(:))
|
|
if (gamma_only) h(nlm(:)) = CONJG(h(nl(:)))
|
|
CALL invfft ('Dense', h, dfftp)
|
|
potential(:) = potential(:) - REAL(h(:))
|
|
end do
|
|
|
|
!! ------------------------------------------------------------------------------------------------------------------------
|
|
deallocate (h_prefactor,h)
|
|
|
|
end subroutine get_potential
|
|
|
|
#else
|
|
|
|
subroutine get_potential(q0, dq0_drho, dq0_dgradrho, N, gradient_rho, u_vdW, potential, my_start_z, my_end_z)
|
|
|
|
USE fft_base, ONLY : dfftp
|
|
USE cell_base, ONLY : alat, at
|
|
|
|
real(dp), intent(in) :: q0(:), gradient_rho(:,:) !! Input arrays holding the value of q0 for all points assigned
|
|
! !! to this processor and the gradient of the charge density for
|
|
! !! points assigned to this processor.
|
|
|
|
real(dp), intent(in) :: dq0_drho(:), dq0_dgradrho(:) !! The derivative of q0 with respect to the charge density and
|
|
! !! gradient of the charge density (almost). See comments in
|
|
! !! the get_q0_on_grid subroutine above.
|
|
|
|
real(dp), intent(inout) :: potential(:) !! The non-local correlation potential for points on the grid over
|
|
! !! the whole cell (not just those assigned to this processor).
|
|
|
|
integer, intent(in) :: N, my_start_z, my_end_z !! The number of neighbors used in the numerical gradient formula
|
|
! !! and the starting and ending z planes for this processor
|
|
|
|
complex(dp), intent(in) :: u_vdW(:,:) !! The functions u_alpha(r) obtained by inverse transforming the
|
|
! !! functions u_alph(k). See equations 14 and 15 in SOLER
|
|
|
|
real(dp), allocatable, save :: d2y_dx2(:,:) !! Second derivatives of P_alpha polynomials for interpolation
|
|
|
|
integer :: i_grid, ix1, ix2, ix3, P_i, nx !! Index variables
|
|
|
|
integer :: q_low, q_hi, q !! Variables to find the bin in the q_mesh that a particular q0
|
|
! !! belongs to (for interpolation).
|
|
|
|
real(dp) :: prefactor !! Intermediate variable used to minimize calculations
|
|
|
|
real(dp), pointer, save :: coefficients(:) !! Pointer to the gradient coefficients. Used to find the derivative
|
|
! !! of the magnitude of the gradient of the charge density with
|
|
! !! respect to the charge density at another point. Equation 13 in SOLER
|
|
|
|
integer, pointer, save :: indices3d(:,:,:) !! A pointer to a rank 3 array that gives the relation between the
|
|
! !! x, y, and z indices of a point and its index in the charge density
|
|
! !! array. Used to easily find neighbors in the x, y, and z directions.
|
|
|
|
real(dp) :: dq, a, b, c, d, e, f !! Inermediate variables used in the interpolation of the polynomials
|
|
|
|
real(dp) :: y(Nqs), dP_dq0, P !! The y values for a given polynomial (all 0 exept for element i of P_i)
|
|
! !! The derivative of P at a given q0 and the value of P at a given q0. Both
|
|
! !! of these are interpolated below
|
|
|
|
real(dp), save :: at_inverse(3,3)
|
|
|
|
logical, save :: have_at_inverse = .false.
|
|
|
|
|
|
if (.not. have_at_inverse) then
|
|
|
|
at_inverse = alat * at
|
|
call invert_3x3_matrix(at_inverse)
|
|
|
|
at_inverse(1,:) = at_inverse(1,:) * dble(dfftp%nr1x)
|
|
at_inverse(2,:) = at_inverse(2,:) * dble(dfftp%nr2x)
|
|
at_inverse(3,:) = at_inverse(3,:) * dble(dfftp%nr3x)
|
|
|
|
have_at_inverse = .true.
|
|
|
|
end if
|
|
|
|
|
|
potential = 0.0D0
|
|
|
|
!! Find the gradient coefficients and the 3d index mapping array if we don't already have it.
|
|
!! -------------------------------------------------------------------------------------------
|
|
|
|
if (.not. associated(indices3d) ) then
|
|
|
|
indices3d => get_3d_indices()
|
|
coefficients => gradient_coefficients()
|
|
|
|
end if
|
|
|
|
!! -------------------------------------------------------------------------------------------
|
|
|
|
|
|
!! Get the second derivatives of the P_i functions for interpolation. We have already calculated
|
|
!! this once but it is very fast and it's just as easy to calculate it again.
|
|
!! ---------------------------------------------------------------------------------------------
|
|
|
|
if (.not. allocated( d2y_dx2) ) then
|
|
|
|
allocate( d2y_dx2(Nqs, Nqs) )
|
|
|
|
call initialize_spline_interpolation(q_mesh, d2y_dx2(:,:))
|
|
|
|
end if
|
|
|
|
!! ---------------------------------------------------------------------------------------------
|
|
|
|
i_grid = 0
|
|
|
|
!! Loop over all the points assigned to this processor. For each point and each q value in the q_mesh,
|
|
!! interpolate P_i and dP_dq0.
|
|
!! --------------------------------------------------------------------------------------------------------------------
|
|
|
|
do ix3 = my_start_z, my_end_z
|
|
do ix2 = 1, dfftp%nr2x
|
|
do ix1 = 1, dfftp%nr1x
|
|
|
|
i_grid = i_grid + 1
|
|
|
|
q_low = 1
|
|
q_hi = Nqs
|
|
|
|
|
|
! Figure out which bin our value of q0 is in in the q_mesh
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
do while ( (q_hi - q_low) > 1)
|
|
|
|
q = int((q_hi + q_low)/2)
|
|
|
|
if (q_mesh(q) > q0(i_grid)) then
|
|
q_hi = q
|
|
else
|
|
q_low = q
|
|
end if
|
|
|
|
end do
|
|
|
|
if (q_hi == q_low) call errore('get_potential','qhi == qlow',1)
|
|
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
dq = q_mesh(q_hi) - q_mesh(q_low)
|
|
|
|
a = (q_mesh(q_hi) - q0(i_grid))/dq
|
|
b = (q0(i_grid) - q_mesh(q_low))/dq
|
|
c = (a**3 - a)*dq**2/6.0D0
|
|
d = (b**3 - b)*dq**2/6.0D0
|
|
e = (3.0D0*a**2 - 1.0D0)*dq/6.0D0
|
|
f = (3.0D0*b**2 - 1.0D0)*dq/6.0D0
|
|
|
|
do P_i = 1, Nqs
|
|
|
|
y = 0.0D0
|
|
y(P_i) = 1.0D0
|
|
|
|
dP_dq0 = (y(q_hi) - y(q_low))/dq - e*d2y_dx2(P_i,q_low) + f*d2y_dx2(P_i,q_hi)
|
|
|
|
P = a*y(q_low) + b*y(q_hi) + c*d2y_dx2(P_i,q_low) + d*d2y_dx2(P_i,q_hi)
|
|
|
|
!! The first term in equation 13 of SOLER
|
|
potential(indices3d(ix1,ix2,ix3)) = potential(indices3d(ix1,ix2,ix3)) + &
|
|
u_vdW(i_grid,P_i)* (P + dP_dq0 * dq0_drho(i_grid))
|
|
|
|
|
|
! Now, loop over all relevant neighbors and calculate the second term in equation 13 of SOLER. Note,
|
|
! that we are using our value of u_vdW and gradients and adding the piece of the potential point i_grid
|
|
! contributes to the neighbor's potential. If the value of q0 at point i_grid is equal to q_cut, the
|
|
! derivative dq0_dq will be 0 so both of dq0_drho and dq0_dgradrho will be 0. Thus, we can safely
|
|
! skip these points.
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
if (q0(i_grid) .ne. q_mesh(Nqs)) then
|
|
|
|
prefactor = u_vdW(i_grid,P_i) * dP_dq0 * dq0_dgradrho(i_grid)
|
|
|
|
do nx = -N,N
|
|
|
|
potential(indices3d(ix1+nx,ix2,ix3)) = potential(indices3d(ix1+nx,ix2,ix3)) &
|
|
+ prefactor * coefficients(nx) &
|
|
* (gradient_rho(i_grid,1)*at_inverse(1,1) + gradient_rho(i_grid,2)*at_inverse(2,1) &
|
|
+ gradient_rho(i_grid,3)*at_inverse(3,1))
|
|
|
|
potential(indices3d(ix1,ix2+nx,ix3)) = potential(indices3d(ix1,ix2+nx,ix3)) &
|
|
+ prefactor * coefficients(nx) &
|
|
* (gradient_rho(i_grid,1)*at_inverse(1,2) + gradient_rho(i_grid,2)*at_inverse(2,2) &
|
|
+ gradient_rho(i_grid,3)*at_inverse(3,2))
|
|
|
|
potential(indices3d(ix1,ix2,ix3+nx)) = potential(indices3d(ix1,ix2,ix3+nx)) &
|
|
+ prefactor * coefficients(nx) &
|
|
* (gradient_rho(i_grid,1)*at_inverse(1,3) + gradient_rho(i_grid,2)*at_inverse(2,3) &
|
|
+ gradient_rho(i_grid,3)*at_inverse(3,3))
|
|
|
|
end do
|
|
|
|
end if
|
|
|
|
!! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
end do
|
|
|
|
end do
|
|
end do
|
|
end do
|
|
|
|
!! ------------------------------------------------------------------------------------------------------------------------
|
|
|
|
end subroutine get_potential
|
|
#endif
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | GRADIENT_COEFFICIENTS |
|
|
!! |_________________________|
|
|
|
|
|
|
!! This routine returns a pointer to an array holding the coefficients for a derivative expansion to some order.
|
|
!! The derivative is found by multiplying the value of the function at a point + or - n away from the sample point by
|
|
!! the coefficient gradient_coefficients(+ or - n) and dividing by the appropriate dx for that direction.
|
|
|
|
|
|
function gradient_coefficients(N)
|
|
|
|
real(dp), allocatable, target, save:: coefficients(:) !! The local array that will hold the coefficients. A pointer to this
|
|
! !! array will be returned by the function
|
|
|
|
integer, intent(in), optional :: N !! The number of neighbors to use on each side for the gradient
|
|
! !! calculation. Can be between 1 (i.e. 3 point derivative formula)
|
|
! !! and 6 (i.e. 13 point derivative formula).
|
|
|
|
real(dp), pointer :: gradient_coefficients(:) !! Pointer to the coefficients array that will be returned
|
|
|
|
|
|
if (.not. allocated(coefficients) ) then
|
|
|
|
if (.not. present(N) ) call errore('gradient_coefficients', 'Number of neighbors for gradient must be specified',2)
|
|
|
|
allocate( coefficients(-N:N) )
|
|
|
|
select case (N)
|
|
|
|
case (1)
|
|
coefficients(-1:1) = (/-0.5D0, 0.0D0, 0.5D0/)
|
|
case (2)
|
|
coefficients(-2:2) = (/0.0833333333333333D0, -0.6666666666666666D0, 0.0D0, &
|
|
0.6666666666666666D0, -0.0833333333333333D0/)
|
|
case (3)
|
|
coefficients(-3:3) = (/-0.0166666666666666D0, 0.15D0, -0.75D0, 0.0D0, 0.75D0, &
|
|
-0.15D0, 0.016666666666666666D0/)
|
|
case (4)
|
|
coefficients(-4:4) = (/0.00357142857143D0, -0.03809523809524D0, 0.2D0, -0.8D0, 0.0D0, &
|
|
0.8D0, -0.2D0, 0.03809523809524D0, -0.00357142857143D0/)
|
|
case (5)
|
|
coefficients(-5:5) = (/-0.00079365079365D0, 0.00992063492063D0, -0.05952380952381D0, &
|
|
0.23809523809524D0, -0.8333333333333333D0, 0.0D0, 0.8333333333333333D0, &
|
|
-0.23809523809524D0, 0.05952380952381D0, -0.00992063492063D0, 0.00079365079365D0/)
|
|
case (6)
|
|
coefficients(-6:6) = (/0.00018037518038D0, -0.00259740259740D0, 0.01785714285714D0, &
|
|
-0.07936507936508D0, 0.26785714285714D0, -0.85714285714286D0, 0.0D0, &
|
|
0.85714285714286D0, -0.26785714285714D0, 0.07936507936508D0, &
|
|
-0.01785714285714D0, 0.00259740259740D0, -0.00018037518038D0/)
|
|
case default
|
|
|
|
call errore('xc_vdW_DF', 'Order of numerical gradient not implemented', 2)
|
|
|
|
end select
|
|
|
|
end if
|
|
|
|
gradient_coefficients => coefficients
|
|
|
|
|
|
end function gradient_coefficients
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | GET_3D_INDICES |
|
|
!! |__________________|
|
|
|
|
!! This routine builds a rank 3 array that holds the indices into the FFT grid for a point with a given
|
|
!! set of x, y, and z indices. The array holds an extra 2N points in each dimension (N to the left and N
|
|
!! to the right) so the code can find the neighbors of edge points easily. This is done by just copying the
|
|
!! first N points in each dimension to the end of that dimension and the end N points to the beginning.
|
|
|
|
|
|
function get_3d_indices(N)
|
|
|
|
USE fft_base, ONLY : dfftp
|
|
|
|
|
|
integer, intent(in), optional :: N !! The number of neighbors in each direction that will
|
|
! !! be used for the gradient formula. If not supplied,
|
|
! !! the code just returns the pointer to the already
|
|
! !! allocated rho_3d array.
|
|
|
|
real(dp) :: dx, dy, dz !!
|
|
integer :: ix1, ix2, ix3, i_grid !! Index variables
|
|
|
|
integer, allocatable, target, save :: rho_3d(:,:,:) !! The local array that will store the indices. Only a pointer
|
|
! !! to this array will be returned.
|
|
|
|
integer, pointer :: get_3d_indices(:,:,:) !! The returned pointer to the rho_3d array of indices.
|
|
|
|
|
|
|
|
!! If the routine has not already been run we set up the rho_3d array by looping over it
|
|
!! and assigning indices to its elements. If this routine has already been run we simply
|
|
!! return a pointer to the existing array.
|
|
!! --------------------------------------------------------------------------------
|
|
|
|
if (.not. allocated(rho_3d)) then
|
|
|
|
! Check to make sure we have been given the number of neighbors since the routine has
|
|
! not been run yet.
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
if (.not. present(N)) then
|
|
|
|
call errore('get_3d_rho','Number of neighbors for numerical derivatives &
|
|
& must be specified',2)
|
|
|
|
end if
|
|
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
allocate( rho_3d(-N+1:dfftp%nr1x+N, -N+1:dfftp%nr2x+N, -N+1:dfftp%nr3x+N) )
|
|
|
|
i_grid = 0
|
|
|
|
do ix3 = 1, dfftp%nr3x
|
|
do ix2 = 1, dfftp%nr2x
|
|
do ix1 = 1, dfftp%nr1x
|
|
|
|
i_grid = i_grid + 1
|
|
|
|
rho_3d(ix1, ix2, ix3) = i_grid
|
|
|
|
end do
|
|
end do
|
|
end do
|
|
|
|
|
|
|
|
! Apply periodic boundary conditions to extend the array by N places in each
|
|
! direction
|
|
! ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
rho_3d(-N+1:0,:,:) = rho_3d(dfftp%nr1x-N+1:dfftp%nr1x, :, :)
|
|
rho_3d(:,-N+1:0,:) = rho_3d(:, dfftp%nr2x-N+1:dfftp%nr2x, :)
|
|
rho_3d(:,:,-N+1:0) = rho_3d(:, :, dfftp%nr3x-N+1:dfftp%nr3x)
|
|
|
|
rho_3d(dfftp%nr1x+1:dfftp%nr1x+N, :, :) = rho_3d(1:N, :, :)
|
|
rho_3d(:, dfftp%nr2x+1:dfftp%nr2x+N, :) = rho_3d(:, 1:N, :)
|
|
rho_3d(:, :, dfftp%nr3x+1:dfftp%nr3x+N) = rho_3d(:, :, 1:N)
|
|
|
|
! +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
end if
|
|
|
|
!! ------------------------------------------------------------------------------------------
|
|
|
|
|
|
!! Return the point to rho_3d
|
|
get_3d_indices => rho_3d
|
|
|
|
|
|
end function get_3d_indices
|
|
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!! ###############################################################################################################
|
|
!! | |
|
|
!! | INVERT_3X3_MATRIX |
|
|
!! |_____________________|
|
|
|
|
!! This routine is just a hard-wired subroutine to invert a 3x3 matrix. It is used to invert the matrix of
|
|
!! unit cell basis vectors to find the gradient and the derivative of the gradient with respect to the
|
|
!! density.
|
|
|
|
subroutine invert_3x3_matrix(M)
|
|
|
|
real(dp), intent(inout) :: M(3,3) !! On input, the 3x3 matrix to be inverted
|
|
! !! On output, the inverse of the 3x3 matrix given
|
|
|
|
real(dp) :: temp(3,3) !! Temporary storage
|
|
|
|
real(dp) :: determinant_M !! The determinant of the input 3x3 matrix
|
|
|
|
|
|
temp = 0.0D0
|
|
|
|
temp(1,1) = M(2,2)*M(3,3) - M(2,3)*M(3,2)
|
|
temp(1,2) = M(1,3)*M(3,2) - M(1,2)*M(3,3)
|
|
temp(1,3) = M(1,2)*M(2,3) - M(1,3)*M(2,2)
|
|
temp(2,1) = M(2,3)*M(3,1) - M(2,1)*M(3,3)
|
|
temp(2,2) = M(1,1)*M(3,3) - M(1,3)*M(3,1)
|
|
temp(2,3) = M(1,3)*M(2,1) - M(1,1)*M(2,3)
|
|
temp(3,1) = M(2,1)*M(3,2) - M(2,2)*M(3,1)
|
|
temp(3,2) = M(1,2)*M(3,1) - M(1,1)*M(3,2)
|
|
temp(3,3) = M(1,1)*M(2,2) - M(1,2)*M(2,1)
|
|
|
|
determinant_M = M(1,1) * (M(2,2)*M(3,3) - M(2,3)*M(3,2)) &
|
|
- M(1,2) * (M(2,1)*M(3,3) - M(2,3)*M(3,1)) &
|
|
+ M(1,3) * (M(2,1)*M(3,2) - M(2,2)*M(3,1))
|
|
|
|
if (abs(determinant_M) > 1e-6) then
|
|
|
|
M = 1.0D0/determinant_M*temp
|
|
|
|
else
|
|
|
|
call errore('invert_3x3_matrix','Matrix is close to singular',1)
|
|
|
|
end if
|
|
|
|
end subroutine invert_3x3_matrix
|
|
|
|
SUBROUTINE print_sigma(sigma, title)
|
|
|
|
real(dp), intent(in) :: sigma(:,:)
|
|
character(len=*), intent(in) :: title
|
|
integer :: l
|
|
|
|
WRITE( stdout, '(10x,A)') TRIM(title)//" stress"
|
|
WRITE( stdout, '(10x,3F13.8)') sigma(1,1), sigma(1,2), sigma(1,3)
|
|
WRITE( stdout, '(10x,3F13.8)') sigma(2,1), sigma(2,2), sigma(2,3)
|
|
WRITE( stdout, '(10x,3F13.8)') sigma(3,1), sigma(3,2), sigma(3,3)
|
|
WRITE( stdout, '(10x)')
|
|
|
|
END SUBROUTINE print_sigma
|
|
|
|
!! ###############################################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
END MODULE vdW_DF
|
|
|