mirror of https://gitlab.com/QEF/q-e.git
3232 lines
112 KiB
Fortran
3232 lines
112 KiB
Fortran
! Copyright (C) 2001-2009 Quantum ESPRESSO group
|
|
! Copyright (C) 2019 Brian Kolb, Timo Thonhauser
|
|
! 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 .
|
|
! ----------------------------------------------------------------------
|
|
|
|
|
|
MODULE vdW_DF
|
|
|
|
!! This module calculates the non-local correlation contribution to the
|
|
!! energy and potential according to:
|
|
!
|
|
!! 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).
|
|
!
|
|
!! The proper spin extension of vdW-DF, i.e. svdW-DF, is derived in:
|
|
!
|
|
!! T. Thonhauser, S. Zuluaga, C.A. Arter, K. Berland, E. Schroder,
|
|
!! and P. Hyldgaard, Phys. Rev. Lett. 115, 136402 (2015).
|
|
!
|
|
!! henceforth referred to as THONHAUSER.
|
|
!
|
|
!! Two review articles show many of the vdW-DF applications:
|
|
!
|
|
!! D.C. Langreth et al., J. Phys.: Condens. Matter 21, 084203 (2009).
|
|
!
|
|
!! K. Berland et al., Rep. Prog. Phys. 78, 066501 (2015).
|
|
!
|
|
!! The method implemented is based on the method of G. Roman-Perez and
|
|
!! J.M. Soler described in:
|
|
!
|
|
!! G. Roman-Perez and J.M. Soler, Phys. Rev. Lett. 103, 096102 (2009).
|
|
!
|
|
!! henceforth referred to as SOLER.
|
|
!
|
|
!
|
|
! xc_vdW_DF and xc_vdW_DF_spin are the driver routines for vdW-DF
|
|
! calculations and are called from Modules/funct.f90. The routines here
|
|
! set up the parallel run (if any) and carry out the calls necessary to
|
|
! calculate the non-local correlation contributions to the energy and
|
|
! potential.
|
|
|
|
|
|
USE kinds, ONLY : dp
|
|
USE constants, ONLY : pi, fpi, e2
|
|
USE mp, ONLY : mp_sum, mp_barrier, mp_get, mp_size, mp_rank, mp_bcast
|
|
USE mp_images, ONLY : intra_image_comm
|
|
USE mp_bands, ONLY : intra_bgrp_comm
|
|
USE io_global, ONLY : stdout, ionode
|
|
USE fft_base, ONLY : dfftp
|
|
USE fft_interfaces, ONLY : fwfft, invfft
|
|
USE control_flags, ONLY : iverbosity, gamma_only
|
|
|
|
! ----------------------------------------------------------------------
|
|
! No implicit variables
|
|
|
|
IMPLICIT NONE
|
|
|
|
|
|
! ----------------------------------------------------------------------
|
|
! By default everything is private
|
|
|
|
PRIVATE
|
|
|
|
|
|
! ----------------------------------------------------------------------
|
|
! Save all objects in this module
|
|
|
|
SAVE
|
|
|
|
|
|
! ----------------------------------------------------------------------
|
|
! Public functions
|
|
|
|
PUBLIC :: xc_vdW_DF, xc_vdW_DF_spin, vdW_DF_stress, &
|
|
vdW_DF_energy, vdW_DF_potential, &
|
|
generate_kernel, interpolate_kernel, &
|
|
initialize_spline_interpolation, spline_interpolation, pw,pw_spin
|
|
|
|
|
|
! ----------------------------------------------------------------------
|
|
! Public variables
|
|
|
|
PUBLIC :: inlc, vdW_DF_analysis, Nr_points, r_max, q_min, q_cut, Nqs, q_mesh
|
|
|
|
|
|
! ----------------------------------------------------------------------
|
|
! General variables
|
|
|
|
INTEGER :: inlc = 1
|
|
! The non-local correlation
|
|
|
|
INTEGER :: vdW_DF_analysis = 0
|
|
! vdW-DF analysis tool as described in PRB 97, 085115 (2018)
|
|
|
|
REAL(DP), PARAMETER :: epsr = 1.0D-12
|
|
! A small number to cut off densities
|
|
|
|
INTEGER :: idx
|
|
! Indexing variable
|
|
|
|
|
|
! ----------------------------------------------------------------------
|
|
! Kernel specific parameters and variables
|
|
|
|
INTEGER, PARAMETER :: Nr_points = 1024
|
|
! The number of radial points (also the number of k points) used in the
|
|
! formation of the kernel functions for each pair of q values.
|
|
! Increasing this value will help in case you get a run-time error
|
|
! saying that you are trying to use a k value that is larger than the
|
|
! largest tabulated k point since the largest k point will be 2*pi/r_max
|
|
! * Nr_points. Memory usage of the vdW_DF piece of PWSCF will increase
|
|
! roughly linearly with this variable.
|
|
|
|
REAL(DP), PARAMETER :: r_max = 100.0D0
|
|
! The value of the maximum radius to use for the real-space kernel
|
|
! functions for each pair of q values. The larger this value is the
|
|
! smaller the smallest k value will be since the smallest k point value
|
|
! is 2*pi/r_max. Be careful though, since this will also decrease the
|
|
! maximum k point value and the vdW_DF code will crash if it encounters
|
|
! a g-vector with a magnitude greater than 2*pi/r_max *Nr_points.
|
|
|
|
REAL(DP), PARAMETER :: dr = r_max/Nr_points, dk = 2.0D0*pi/r_max
|
|
! Real space and k-space spacing of grid points.
|
|
|
|
REAL(DP), PARAMETER :: q_min = 1.0D-5, q_cut = 5.0D0
|
|
! The maximum and minimum values of q. During a vdW run, values of q0
|
|
! found larger than q_cut will be saturated (SOLER equation 5) to q_cut.
|
|
|
|
INTEGER, PARAMETER :: Nqs = 20
|
|
REAL(DP), PARAMETER, DIMENSION(Nqs) :: q_mesh = (/ &
|
|
q_min , 0.0449420825586261D0, 0.0975593700991365D0, 0.159162633466142D0, &
|
|
0.231286496836006D0, 0.315727667369529D0 , 0.414589693721418D0 , 0.530335368404141D0, &
|
|
0.665848079422965D0, 0.824503639537924D0 , 1.010254382520950D0 , 1.227727621364570D0, &
|
|
1.482340921174910D0, 1.780437058359530D0 , 2.129442028133640D0 , 2.538050036534580D0, &
|
|
3.016440085356680D0, 3.576529545442460D0 , 4.232271035198720D0 , q_cut /)
|
|
|
|
! The above two parameters define the q mesh to be used in the vdW_DF
|
|
! code. These are perhaps the most important to have set correctly.
|
|
! Increasing the number of q points will DRAMATICALLY increase the
|
|
! memory usage of the vdW_DF code because the memory consumption depends
|
|
! quadratically on the number of q points in the mesh. Increasing the
|
|
! number of q points may increase accuracy of the vdW_DF code, although,
|
|
! in testing it was found to have little effect. The largest value of
|
|
! the q mesh is q_cut. All values of q0 (DION equation 11) larger than
|
|
! this value during a run will be saturated to this value using equation
|
|
! 5 of SOLER. In testing, increasing the value of q_cut was found to
|
|
! have little impact on the results, although it is possible that in
|
|
! some systems it may be more important. Always make sure that the
|
|
! variable Nqs is consistent with the number of q points that are
|
|
! actually in the variable q_mesh. Also, do not set any q value to 0.
|
|
! This will cause an infinity in the Fourier transform.
|
|
|
|
INTEGER, PARAMETER :: Nintegration_points = 256
|
|
! Number of integration points for real-space kernel generation (see
|
|
! DION equation 14). This is how many a's and b's there will be.
|
|
|
|
REAL(DP), PARAMETER :: a_min = 0.0D0, a_max = 64.0D0
|
|
! Min/max values for the a and b integration in DION equation 14.
|
|
|
|
REAL(DP) :: kernel( 0:Nr_points, Nqs, Nqs ), d2phi_dk2( 0:Nr_points, Nqs, Nqs )
|
|
! Matrices holding the Fourier transformed kernel function and its
|
|
! second derivative for each pair of q values. The ordering is
|
|
! kernel(k_point, q1_value, q2_value).
|
|
|
|
REAL(DP) :: W_ab( Nintegration_points, Nintegration_points )
|
|
! Defined in DION equation 16.
|
|
|
|
REAL(DP) :: a_points( Nintegration_points ), a_points2( Nintegration_points )
|
|
! The values of the "a" points (DION equation 14) and their squares.
|
|
|
|
CONTAINS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | functions |
|
|
! |_____________|
|
|
!
|
|
! Functions to be used in get_q0_on_grid, get_q0_on_grid_spin, and
|
|
! phi_value.
|
|
|
|
FUNCTION Fs(s)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: s, Fs, Z_ab = 0.0D0
|
|
|
|
IF ( inlc == 1 .OR. inlc == 3 ) THEN
|
|
Z_ab = -0.8491D0
|
|
ELSE IF ( inlc == 2 .OR. inlc == 4 .OR. inlc == 5 ) THEN
|
|
Z_ab = -1.887D0
|
|
END IF
|
|
|
|
Fs = 1.0D0 - Z_ab * s * s / 9.0D0
|
|
|
|
END FUNCTION Fs
|
|
|
|
|
|
|
|
|
|
FUNCTION dFs_ds(s)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: s, dFs_ds, Z_ab = 0.0D0
|
|
REAL(DP), PARAMETER :: prefac = -2.0D0/9.0D0
|
|
|
|
IF ( inlc == 1 .OR. inlc == 3 ) THEN
|
|
Z_ab = -0.8491D0
|
|
ELSE IF ( inlc == 2 .OR. inlc == 4 .OR. inlc == 5 ) THEN
|
|
Z_ab = -1.887D0
|
|
END IF
|
|
|
|
dFs_ds = prefac * s * Z_ab
|
|
|
|
END FUNCTION dFs_ds
|
|
|
|
|
|
|
|
|
|
FUNCTION kF(rho)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: rho, kF
|
|
REAL(DP), PARAMETER :: ex = 1.0D0/3.0D0
|
|
|
|
kF = ( 3.0D0 * pi * pi * rho )**ex
|
|
|
|
END FUNCTION kF
|
|
|
|
|
|
|
|
|
|
FUNCTION dkF_drho(rho)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: rho, dkF_drho
|
|
REAL(DP), PARAMETER :: prefac = 1.0D0/3.0D0
|
|
|
|
dkF_drho = prefac * kF(rho) / rho
|
|
|
|
END FUNCTION dkF_drho
|
|
|
|
|
|
|
|
|
|
FUNCTION ds_drho(rho, s)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: rho, s, ds_drho
|
|
|
|
ds_drho = -s * ( dkF_drho(rho) / kF(rho) + 1.0D0 / rho )
|
|
|
|
END FUNCTION ds_drho
|
|
|
|
|
|
|
|
|
|
FUNCTION ds_dgradrho(rho)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: rho, ds_dgradrho
|
|
|
|
ds_dgradrho = 0.5D0 / (kF(rho) * rho)
|
|
|
|
END FUNCTION ds_dgradrho
|
|
|
|
|
|
|
|
|
|
FUNCTION dqx_drho(rho, s)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: rho, s, dqx_drho
|
|
|
|
dqx_drho = dkF_drho(rho) * Fs(s) + kF(rho) * dFs_ds(s) * ds_drho(rho, s)
|
|
|
|
END FUNCTION dqx_drho
|
|
|
|
|
|
|
|
|
|
FUNCTION h_function(y)
|
|
|
|
IMPLICIT NONE
|
|
REAL(DP) :: y, y2, y4, h_function
|
|
REAL(DP), PARAMETER :: g1 = fpi/9.0D0 ! vdW-DF1/2
|
|
REAL(DP), PARAMETER :: a3 = 0.94950D0, g3 = 1.12D0, g32 = g3*g3 ! vdW-DF3-opt1
|
|
REAL(DP), PARAMETER :: a4 = 0.28248D0, g4 = 1.29D0, g42 = g4*g4 ! vdW-DF3-opt2
|
|
REAL(DP), PARAMETER :: a5 = 2.01059D0, b5 = 8.17471D0, g5 = 1.84981D0, & ! vdW-DF-C6
|
|
AA = ( b5 + a5*(a5/2.0D0-g5) ) / ( 1.0D0+g5-a5 ) !
|
|
|
|
|
|
y2 = y*y
|
|
|
|
IF ( inlc == 1 .OR. inlc == 2 ) THEN
|
|
|
|
h_function = 1.0D0 - EXP( -g1*y2 )
|
|
|
|
ELSE IF ( inlc == 3 ) THEN
|
|
|
|
y4 = y2*y2
|
|
h_function = 1.0D0 - 1.0D0 / ( 1.0D0 + g3*y2 + g32*y4 + a3*y4*y4 )
|
|
|
|
ELSE IF ( inlc == 4 ) THEN
|
|
|
|
y4 = y2*y2
|
|
h_function = 1.0D0 - 1.0D0 / ( 1.0D0 + g4*y2 + g42*y4 + a4*y4*y4 )
|
|
|
|
ELSE IF ( inlc == 5 ) THEN
|
|
|
|
y4 = y2*y2
|
|
h_function = 1.0D0 - ( 1.0D0 + ( (a5-g5)*y2 + AA*y4 ) / ( 1.0D0+AA*y2 ) ) * EXP( -a5*y2 )
|
|
|
|
END IF
|
|
|
|
END FUNCTION
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | XC_VDW_DF |
|
|
! |_____________|
|
|
|
|
SUBROUTINE xc_vdW_DF (rho_valence, rho_core, etxc, vtxc, v)
|
|
!
|
|
!! Driver routine for vdW-DF calculations, called from Modules/funct.f90.
|
|
!! The routine here sets up the parallel run (if any) and carry out the
|
|
!! calls necessary to calculate the non-local correlation contributions
|
|
!! to the energy and potential.
|
|
|
|
USE gvect, ONLY : ngm, g
|
|
USE cell_base, ONLY : omega, tpiba
|
|
|
|
IMPLICIT NONE
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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 ! Indexing variables over grid points,
|
|
! theta functions, and processors.
|
|
|
|
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 5 in SOLER.
|
|
|
|
REAL(DP), ALLOCATABLE :: grad_rho(:,:) ! The gradient of the charge density. The
|
|
! format is as follows:
|
|
! grad_rho(cartesian_component,grid_point).
|
|
|
|
REAL(DP), ALLOCATABLE :: potential(:) ! The vdW contribution to the potential.
|
|
|
|
REAL(DP), ALLOCATABLE :: dq0_drho(:) ! The derivative of the saturated q0
|
|
! (equation 5 of SOLER) with respect
|
|
! to the charge density (see
|
|
! get_q0_on_grid subroutine for details).
|
|
|
|
REAL(DP), ALLOCATABLE :: dq0_dgradrho(:) ! The derivative of the saturated q0
|
|
! (equation 5 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 8 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 11
|
|
! in SOLER. They are formatted as follows:
|
|
! 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.
|
|
|
|
LOGICAL, SAVE :: first_iteration = .TRUE. ! Whether this is the first time this
|
|
! routine has been called.
|
|
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Write out the vdW-DF information and initialize the calculation.
|
|
|
|
IF ( first_iteration ) THEN
|
|
IF ( inlc > 5 ) CALL errore( 'xc_vdW_DF', 'inlc not implemented', 1 )
|
|
CALL generate_kernel
|
|
IF ( ionode ) CALL vdW_info(1)
|
|
first_iteration = .FALSE.
|
|
END IF
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Allocate arrays. nnr is a PWSCF variable that holds the number of
|
|
! points assigned to a given processor.
|
|
|
|
allocate( total_rho(dfftp%nnr), grad_rho(3,dfftp%nnr), &
|
|
potential(dfftp%nnr), thetas(dfftp%nnr, Nqs), &
|
|
q0(dfftp%nnr), dq0_drho(dfftp%nnr), dq0_dgradrho(dfftp%nnr) )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Add together the valence and core charge densities to get the total
|
|
! charge density. Note that rho_core is not the true core density and
|
|
! it is only non-zero for pseudopotentials with non-local core
|
|
! corrections.
|
|
|
|
total_rho = rho_valence(:,1) + rho_core(:)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the gradient in reciprocal space using FFT.
|
|
|
|
CALL fft_gradient_r2r (dfftp, total_rho, g, grad_rho)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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 5 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. This routine also calculates the thetas.
|
|
|
|
CALL get_q0_on_grid (total_rho, grad_rho, q0, dq0_drho, dq0_dgradrho, thetas)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Carry out the integration in equation 8 of SOLER. This also turns
|
|
! the theta arrays into the precursor to the u_i(k) array which is
|
|
! inverse fourier transformed to get the u_i(r) functions of SOLER
|
|
! equation 11. Add the energy we find to the output variable etxc.
|
|
|
|
CALL vdW_DF_energy (thetas, Ec_nl)
|
|
etxc = etxc + Ec_nl
|
|
|
|
IF ( iverbosity > 0 ) THEN
|
|
CALL mp_sum(Ec_nl, intra_bgrp_comm)
|
|
IF ( ionode ) THEN
|
|
WRITE(stdout,'(/ / A)') " -----------------------------------------------"
|
|
WRITE(stdout,'(A, F15.8, A)') " Non-local corr. energy = ", Ec_nl, " Ry"
|
|
WRITE(stdout,'(A /)') " -----------------------------------------------"
|
|
END IF
|
|
END IF
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the potential. This is calculated via equation 10
|
|
! of SOLER, using the u_i(r) calculated from quations 11 and 12 of
|
|
! SOLER. Each processor allocates the array to be the size of the full
|
|
! grid because, as can be seen in SOLER equation 10, processors need
|
|
! to access grid points outside their allocated regions. Begin by
|
|
! FFTing the u_i(k) to get the u_i(r) of SOLER equation 11.
|
|
|
|
DO theta_i = 1, Nqs
|
|
CALL invfft('Rho', thetas(:,theta_i), dfftp)
|
|
END DO
|
|
|
|
CALL vdW_DF_potential (q0, dq0_drho, dq0_dgradrho, grad_rho, thetas, potential)
|
|
v(:,1) = v(:,1) + e2 * potential(:)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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 ( total_rho, grad_rho, potential, thetas, q0, dq0_drho, dq0_dgradrho )
|
|
|
|
END SUBROUTINE xc_vdW_DF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | XC_VDW_DF_spin |
|
|
! |__________________|
|
|
!
|
|
!
|
|
SUBROUTINE xc_vdW_DF_spin (rho_valence, rho_core, etxc, vtxc, v)
|
|
|
|
!! This subroutine is as similar to \(\texttt{xc_vdW_DF}\) as possible,
|
|
!! but handles the collinear \(\text{nspin}=2\) case.
|
|
|
|
USE gvect, ONLY : ngm, g
|
|
USE cell_base, ONLY : omega, tpiba
|
|
|
|
IMPLICIT NONE
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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 ! 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 5 in SOLER.
|
|
|
|
REAL(DP), ALLOCATABLE :: grad_rho(:,:) ! The gradient of the charge density. The
|
|
! format is as follows:
|
|
! grad_rho(cartesian_component,grid_point).
|
|
REAL(DP), ALLOCATABLE :: grad_rho_up(:,:) ! The gradient of the up charge density.
|
|
! Same format as grad_rho.
|
|
REAL(DP), ALLOCATABLE :: grad_rho_down(:,:) ! The gradient of the down charge density.
|
|
! Same format as grad_rho.
|
|
|
|
REAL(DP), ALLOCATABLE :: potential_up(:) ! The vdW contribution to the potential.
|
|
REAL(DP), ALLOCATABLE :: potential_down(:) ! The vdW contribution to the potential.
|
|
|
|
REAL(DP), ALLOCATABLE :: dq0_drho_up(:) ! The derivative of the saturated q0
|
|
REAL(DP), ALLOCATABLE :: dq0_drho_down(:) ! (equation 5 of SOLER) with respect
|
|
! to the charge density (see
|
|
! get_q0_on_grid subroutine for details).
|
|
|
|
REAL(DP), ALLOCATABLE :: dq0_dgradrho_up(:) ! The derivative of the saturated q0
|
|
REAL(DP), ALLOCATABLE :: dq0_dgradrho_down(:) ! (equation 5 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 8 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 11
|
|
! in SOLER. They are formatted as follows:
|
|
! 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 (up and down)
|
|
! and core charge. This just holds the piece
|
|
! assigned to this processor.
|
|
REAL(DP), ALLOCATABLE :: rho_up(:) ! This is the just the up valence charge.
|
|
! This just holds the piece assigned
|
|
! to this processor.
|
|
REAL(DP), ALLOCATABLE :: rho_down(:) ! This is the just the down valence charge.
|
|
! This just holds the piece assigned
|
|
! to this processor.
|
|
|
|
LOGICAL, SAVE :: first_iteration = .TRUE. ! Whether this is the first time this
|
|
! routine has been called.
|
|
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Write out the vdW-DF information and initialize the calculation.
|
|
|
|
IF ( first_iteration ) THEN
|
|
IF ( inlc > 5 ) CALL errore( 'xc_vdW_DF_spin', 'inlc not implemented', 1 )
|
|
CALL generate_kernel
|
|
IF ( ionode ) CALL vdW_info(2)
|
|
first_iteration = .FALSE.
|
|
END IF
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Allocate arrays. nnr is a PWSCF variable that holds the number of
|
|
! points assigned to a given processor.
|
|
|
|
ALLOCATE( total_rho(dfftp%nnr), rho_up(dfftp%nnr), rho_down(dfftp%nnr), &
|
|
grad_rho(3,dfftp%nnr), grad_rho_up(3,dfftp%nnr), grad_rho_down(3,dfftp%nnr), &
|
|
potential_up(dfftp%nnr), potential_down(dfftp%nnr), thetas(dfftp%nnr, Nqs), &
|
|
q0(dfftp%nnr), dq0_drho_up(dfftp%nnr), dq0_dgradrho_up(dfftp%nnr), &
|
|
dq0_drho_down(dfftp%nnr), dq0_dgradrho_down(dfftp%nnr) )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Add together the valence and core charge densities to get the total
|
|
! charge density. Note that rho_core is not the true core density and
|
|
! it is only non-zero for pseudopotentials with non-local core
|
|
! corrections.
|
|
|
|
rho_up = ( rho_valence(:,1) + rho_valence(:,2) + rho_core(:) )*0.5D0
|
|
rho_down = ( rho_valence(:,1) - rho_valence(:,2) + rho_core(:) )*0.5D0
|
|
total_rho = rho_up + rho_down
|
|
|
|
#if defined (__SPIN_BALANCED)
|
|
rho_up = total_rho*0.5D0
|
|
rho_down = rho_up
|
|
WRITE(stdout,'(/,/," Performing spin-balanced Ecnl calculation!")')
|
|
#endif
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the gradient in reciprocal space using FFT.
|
|
|
|
CALL fft_gradient_r2r (dfftp, total_rho, g, grad_rho)
|
|
CALL fft_gradient_r2r (dfftp, rho_up, g, grad_rho_up)
|
|
CALL fft_gradient_r2r (dfftp, rho_down, g, grad_rho_down)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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 5 of SOLER. In the spin case, q0 is defined by
|
|
! equation 8 (and text above that equation) of THONHAUSER. 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_spin (total_rho, rho_up, rho_down, grad_rho, &
|
|
grad_rho_up, grad_rho_down, q0, dq0_drho_up, dq0_drho_down, &
|
|
dq0_dgradrho_up, dq0_dgradrho_down, thetas)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Carry out the integration in equation 8 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 11. Add the energy we find to the output variable etxc.
|
|
|
|
CALL vdW_DF_energy(thetas, Ec_nl)
|
|
etxc = etxc + Ec_nl
|
|
|
|
IF ( iverbosity > 0 ) THEN
|
|
CALL mp_sum(Ec_nl, intra_bgrp_comm)
|
|
IF (ionode) THEN
|
|
WRITE(stdout,'(/ / A)') " -----------------------------------------------"
|
|
WRITE(stdout,'(A, F15.8, A)') " Non-local corr. energy = ", Ec_nl, " Ry"
|
|
WRITE(stdout,'(A /)') " -----------------------------------------------"
|
|
END IF
|
|
END IF
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the potential. This is calculated via equation 10
|
|
! of SOLER, using the u_i(r) calculated from quations 11 and 12 of
|
|
! SOLER. Each processor allocates the array to be the size of the full
|
|
! grid because, as can be seen in SOLER equation 10, processors need
|
|
! to access grid points outside their allocated regions. Begin by
|
|
! FFTing the u_i(k) to get the u_i(r) of SOLER equation 11.
|
|
|
|
DO theta_i = 1, Nqs
|
|
CALL invfft('Rho', thetas(:,theta_i), dfftp)
|
|
END DO
|
|
|
|
CALL vdW_DF_potential (q0, dq0_drho_up , dq0_dgradrho_up , grad_rho_up , thetas, potential_up )
|
|
CALL vdW_DF_potential (q0, dq0_drho_down, dq0_dgradrho_down, grad_rho_down, thetas, potential_down)
|
|
|
|
v(:,1) = v(:,1) + e2 * potential_up (:)
|
|
v(:,2) = v(:,2) + e2 * potential_down(:)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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) + &
|
|
rho_valence(i_grid,2)) * 0.5_dp * potential_up (i_grid) &
|
|
+ e2 * grid_cell_volume * (rho_valence(i_grid,1) - &
|
|
rho_valence(i_grid,2)) * 0.5_dp * potential_down(i_grid)
|
|
END DO
|
|
|
|
DEALLOCATE( total_rho, rho_up, rho_down, grad_rho, grad_rho_up, grad_rho_down, &
|
|
potential_up, potential_down, thetas, &
|
|
q0, dq0_drho_up, dq0_dgradrho_up, dq0_drho_down, dq0_dgradrho_down )
|
|
|
|
END SUBROUTINE xc_vdW_DF_spin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | GET_Q0_ON_GRID |
|
|
! |__________________|
|
|
!
|
|
!
|
|
SUBROUTINE get_q0_on_grid (total_rho, grad_rho, q0, dq0_drho, dq0_dgradrho, thetas)
|
|
!! This routine first calculates the q value defined in (DION equations
|
|
!! 11 and 12), then saturates it according to (SOLER equation 5). More
|
|
!! specifically it calculates the following:
|
|
!
|
|
!! * \(\text{q0(ir)}\) = saturated value of q;
|
|
!! * \(\text{dq0_drho(ir)} = \text{total_rho} * d\text{q0} /d\text{rho}\)
|
|
!! * \(\text{dq0_dgradrho} = \text{total_rho} / |\text{grad_rho}| * d\text{q0}
|
|
!! / d |\text{grad_rho}|\)
|
|
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), INTENT(IN) :: total_rho(:), grad_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.
|
|
COMPLEX(DP), INTENT(INOUT):: thetas(:,:) ! The thetas from SOLER.
|
|
|
|
INTEGER, PARAMETER :: m_cut = 12 ! How many terms to include in the sum
|
|
! of SOLER equation 5.
|
|
|
|
REAL(DP) :: rho ! Local variable for the density.
|
|
REAL(DP) :: r_s ! Wigner-Seitz radius.
|
|
REAL(DP) :: s ! Reduced gradient.
|
|
REAL(DP) :: q
|
|
REAL(DP) :: ec
|
|
REAL(DP) :: dq0_dq ! The derivative of the saturated
|
|
! q0 with respect to q.
|
|
|
|
INTEGER :: i_grid ! Indexing variable.
|
|
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Initialize q0-related arrays.
|
|
|
|
q0(:) = q_cut
|
|
dq0_drho(:) = 0.0D0
|
|
dq0_dgradrho(:) = 0.0D0
|
|
|
|
|
|
DO i_grid = 1, dfftp%nnr
|
|
|
|
rho = total_rho(i_grid)
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! 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 ( rho < epsr ) CYCLE
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Calculate some intermediate values needed to find q.
|
|
|
|
r_s = ( 3.0D0 / (4.0D0*pi*rho) )**(1.0D0/3.0D0)
|
|
|
|
s = SQRT( grad_rho(1,i_grid)**2 + grad_rho(2,i_grid)**2 + grad_rho(3,i_grid)**2 ) / &
|
|
(2.0D0 * kF(rho) * rho )
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! This is the q value defined in equations 11 and 12 of DION.
|
|
! Use pw() from flib/functionals.f90 to get qc = kf/eps_x * eps_c.
|
|
!
|
|
CALL pw(r_s, 1, ec, dq0_drho(i_grid))
|
|
q = -4.0D0*pi/3.0D0 * ec + kF(rho) * Fs(s)
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Bring q into its proper bounds.
|
|
|
|
CALL saturate_q ( q, q_cut, q0(i_grid), dq0_dq )
|
|
IF (q0(i_grid) < q_min) q0(i_grid) = q_min
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Here we find derivatives. These are actually the density times
|
|
! the derivative of q0 with respect to rho and grad_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_dgrad_rho = dP_dq0 * [rho * dq0_dq * dq_dgrad_rho]
|
|
!
|
|
! The parts in square brackets are what is calculated here. The
|
|
! dP_dq0 term will be interpolated later.
|
|
|
|
dq0_drho(i_grid) = dq0_dq * rho * ( -4.0D0*pi/3.0D0 * &
|
|
(dq0_drho(i_grid) - ec)/rho + dqx_drho(rho, s) )
|
|
dq0_dgradrho(i_grid) = dq0_dq * rho * kF(rho) * dFs_ds(s) * ds_dgradrho(rho)
|
|
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the theta functions of SOLER equation 8. These are
|
|
! defined as
|
|
!
|
|
! rho * P_i(q0(rho, grad_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 5 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 8 of SOLER). Start by interpolating the
|
|
! P_i polynomials defined in equation 3 in SOLER for the particular q0
|
|
! values we have.
|
|
|
|
CALL spline_interpolation (q_mesh, q0, thetas)
|
|
|
|
DO i_grid = 1, dfftp%nnr
|
|
thetas(i_grid,:) = thetas(i_grid,:) * total_rho(i_grid)
|
|
END DO
|
|
|
|
DO idx = 1, Nqs
|
|
CALL fwfft ('Rho', thetas(:,idx), dfftp)
|
|
END DO
|
|
|
|
END SUBROUTINE get_q0_on_grid
|
|
|
|
|
|
!-----------------------------------------------------------------------
|
|
SUBROUTINE pw( rs, iflag, ec, vc )
|
|
!-----------------------------------------------------------------------
|
|
! --A provisional copy of the pw routine in XC lib to avoid external calls--
|
|
!! * iflag=1: J.P. Perdew and Y. Wang, PRB 45, 13244 (1992)
|
|
!! * iflag=2: G. Ortiz and P. Ballone, PRB 50, 1391 (1994)
|
|
!
|
|
IMPLICIT NONE
|
|
REAL(DP), INTENT(IN) :: rs
|
|
INTEGER, INTENT(IN) :: iflag
|
|
REAL(DP), INTENT(OUT) :: ec, vc
|
|
!
|
|
REAL(DP), PARAMETER :: a=0.031091d0, b1=7.5957d0, b2=3.5876d0, c0=a, &
|
|
c1=0.046644d0, c2=0.00664d0, c3=0.01043d0, d0=0.4335d0, &
|
|
d1=1.4408d0
|
|
REAL(DP) :: lnrs, rs12, rs32, rs2, om, dom, olog
|
|
REAL(DP) :: a1(2), b3(2), b4(2)
|
|
DATA a1 / 0.21370d0, 0.026481d0 /, b3 / 1.6382d0, -0.46647d0 /, &
|
|
b4 / 0.49294d0, 0.13354d0 /
|
|
! high- and low-density formulae implemented but not used in PW case
|
|
! (reason: inconsistencies in PBE/PW91 functionals).
|
|
IF ( rs < 1d0 .AND. iflag == 2 ) THEN
|
|
! high density formula
|
|
lnrs = LOG(rs)
|
|
ec = c0 * lnrs - c1 + c2 * rs * lnrs - c3 * rs
|
|
vc = c0 * lnrs - (c1 + c0 / 3.d0) + 2.d0 / 3.d0 * c2 * rs * &
|
|
lnrs - (2.d0 * c3 + c2) / 3.d0 * rs
|
|
ELSEIF ( rs > 100.d0 .AND. iflag == 2 ) THEN
|
|
! low density formula
|
|
ec = - d0 / rs + d1 / rs**1.5d0
|
|
vc = - 4.d0 / 3.d0 * d0 / rs + 1.5d0 * d1 / rs**1.5d0
|
|
ELSE
|
|
! interpolation formula
|
|
rs12 = SQRT(rs)
|
|
rs32 = rs * rs12
|
|
rs2 = rs**2
|
|
om = 2.d0*a*( b1*rs12 + b2*rs + b3(iflag) * rs32 + b4(iflag)*rs2 )
|
|
dom = 2.d0*a*( 0.5d0 * b1 * rs12 + b2 * rs + 1.5d0 * b3(iflag) * &
|
|
rs32 + 2.d0 * b4(iflag) * rs2 )
|
|
olog = LOG( 1.d0 + 1.0d0 / om )
|
|
!
|
|
ec = - 2.d0 * a * (1.d0 + a1(iflag) * rs) * olog
|
|
vc = - 2.d0 * a * (1.d0 + 2.d0 / 3.d0 * a1(iflag) * rs) &
|
|
* olog - 2.d0 / 3.d0 * a * (1.d0 + a1(iflag) * rs) * dom / &
|
|
(om * (om + 1.d0) )
|
|
ENDIF
|
|
!
|
|
RETURN
|
|
!
|
|
END SUBROUTINE pw
|
|
!---------------------
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | GET_Q0_ON_GRID_spin |
|
|
! |_______________________|
|
|
|
|
SUBROUTINE get_q0_on_grid_spin (total_rho, rho_up, rho_down, grad_rho, &
|
|
grad_rho_up, grad_rho_down, q0, dq0_drho_up, dq0_drho_down, &
|
|
dq0_dgradrho_up, dq0_dgradrho_down, thetas)
|
|
|
|
!! Find the value of \(\text{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 5 of SOLER. In the spin case, q0 is defined by
|
|
!! equation 8 (and text above that equation) of THONHAUSER. This
|
|
!! routine also returns the derivatives of the \(\text{q0}\)s with respect to the
|
|
!! charge-density and the gradient of the charge-density. These are
|
|
!! needed for the potential.
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), INTENT(IN) :: total_rho(:), grad_rho(:,:) ! Input variables.
|
|
REAL(DP), INTENT(IN) :: rho_up(:), grad_rho_up(:,:) ! Input variables.
|
|
REAL(DP), INTENT(IN) :: rho_down(:), grad_rho_down(:,:) ! Input variables.
|
|
|
|
REAL(DP), INTENT(OUT) :: q0(:), dq0_drho_up(:), dq0_drho_down(:) ! Output variables.
|
|
REAL(DP), INTENT(OUT) :: dq0_dgradrho_up(:), dq0_dgradrho_down(:) ! Output variables.
|
|
COMPLEX(DP), INTENT(INOUT) :: thetas(:,:) ! The thetas from SOLER.
|
|
|
|
REAL(DP) :: rho, up, down ! Local copy of densities.
|
|
REAL(DP) :: zeta ! Spin polarization.
|
|
REAL(DP) :: r_s ! Wigner-Seitz radius.
|
|
REAL(DP) :: q, qc, qx, qx_up, qx_down ! q for exchange and correlation.
|
|
REAL(DP) :: q0x_up, q0x_down ! Saturated q values.
|
|
REAL(DP) :: fac
|
|
REAL(DP) :: ec, vc(2)
|
|
REAL(DP) :: dq0_dq, dq0x_up_dq, dq0x_down_dq ! Derivative of q0 w.r.t q.
|
|
REAL(DP) :: dqc_drho_up, dqc_drho_down ! Intermediate values.
|
|
REAL(DP) :: dqx_drho_up, dqx_drho_down ! Intermediate values.
|
|
REAL(DP) :: s_up, s_down ! Reduced gradients.
|
|
INTEGER :: i_grid ! Indexing variable.
|
|
LOGICAL :: calc_qx_up, calc_qx_down
|
|
|
|
|
|
fac = 2.0D0**(-1.0D0/3.0D0)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Initialize q0-related arrays.
|
|
|
|
q0(:) = q_cut
|
|
dq0_drho_up(:) = 0.0D0
|
|
dq0_drho_down(:) = 0.0D0
|
|
dq0_dgradrho_up(:) = 0.0D0
|
|
dq0_dgradrho_down(:) = 0.0D0
|
|
|
|
|
|
DO i_grid = 1, dfftp%nnr
|
|
|
|
rho = total_rho(i_grid)
|
|
up = rho_up(i_grid)
|
|
down = rho_down(i_grid)
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! 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 ( rho < epsr ) CYCLE
|
|
|
|
calc_qx_up = .TRUE.
|
|
calc_qx_down = .TRUE.
|
|
|
|
IF ( up < epsr/2.0D0 ) calc_qx_up = .FALSE.
|
|
IF ( down < epsr/2.0D0 ) calc_qx_down = .FALSE.
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! The spin case is numerically even more tricky and we have to
|
|
! saturate each spin channel separately. Note that we are
|
|
! saturating at a higher value here, so that very large q values
|
|
! get saturated to exactly q_cut in the second, overall saturation.
|
|
|
|
q0x_up = 0.0D0
|
|
q0x_down = 0.0D0
|
|
dqx_drho_up = 0.0D0
|
|
dqx_drho_down = 0.0D0
|
|
|
|
|
|
IF ( calc_qx_up ) THEN
|
|
s_up = SQRT( grad_rho_up(1,i_grid)**2 + grad_rho_up(2,i_grid)**2 + &
|
|
grad_rho_up(3,i_grid)**2 ) / (2.0D0 * kF(up) * up)
|
|
qx_up = kF(2.0D0*up) * Fs(fac*s_up)
|
|
CALL saturate_q (qx_up, 4.0D0*q_cut, q0x_up, dq0x_up_dq)
|
|
END IF
|
|
|
|
IF ( calc_qx_down ) THEN
|
|
s_down = SQRT( grad_rho_down(1,i_grid)**2 + grad_rho_down(2,i_grid)**2 + &
|
|
grad_rho_down(3,i_grid)**2) / (2.0D0 * kF(down) * down)
|
|
qx_down = kF(2.0D0*down) * Fs(fac*s_down)
|
|
CALL saturate_q (qx_down, 4.0D0*q_cut, q0x_down, dq0x_down_dq)
|
|
END IF
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! This is the q value defined in equations 11 and 12 of DION and
|
|
! equation 8 of THONHAUSER (also see text above that equation).
|
|
|
|
r_s = ( 3.0D0 / (4.0D0*pi*rho) )**(1.0D0/3.0D0)
|
|
zeta = (up - down) / rho
|
|
IF ( ABS(zeta) > 1.0D0 ) zeta = SIGN(1.0D0, zeta)
|
|
call pw_spin( r_s, zeta, ec, vc(1), vc(2) )
|
|
dqc_drho_up = vc(1)
|
|
dqc_drho_down = vc(2)
|
|
|
|
qx = ( up * q0x_up + down * q0x_down ) / rho
|
|
qc = -4.0D0*pi/3.0D0 * ec
|
|
q = qx + qc
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Bring q into its proper bounds.
|
|
|
|
CALL saturate_q (q, q_cut, q0(i_grid), dq0_dq)
|
|
IF (q0(i_grid) < q_min) q0(i_grid) = q_min
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Here we find derivatives. These are actually the density times
|
|
! the derivative of q0 with respect to rho and grad_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_dgrad_rho = dP_dq0 * [rho * dq0_dq * dq_dgrad_rho]
|
|
!
|
|
! The parts in square brackets are what is calculated here. The
|
|
! dP_dq0 term will be interpolated later.
|
|
|
|
IF ( calc_qx_up ) THEN
|
|
dqx_drho_up = 2.0D0*dq0x_up_dq*up*dqx_drho(2.0D0*up, fac*s_up) + q0x_up*down/rho
|
|
dq0_dgradrho_up (i_grid) = 2.0D0 * dq0_dq * dq0x_up_dq * up * kF(2.0D0*up) * &
|
|
dFs_ds(fac*s_up) * ds_dgradrho(2.0D0*up)
|
|
END IF
|
|
|
|
IF ( calc_qx_down ) THEN
|
|
dqx_drho_down = 2.0D0*dq0x_down_dq*down*dqx_drho(2.0D0*down, fac*s_down) + q0x_down*up/rho
|
|
dq0_dgradrho_down(i_grid) = 2.0D0 * dq0_dq * dq0x_down_dq * down * kF(2.0D0*down) * &
|
|
dFs_ds(fac*s_down) * ds_dgradrho(2.0D0*down)
|
|
END IF
|
|
|
|
IF ( calc_qx_down ) dqx_drho_up = dqx_drho_up - q0x_down*down/rho
|
|
IF ( calc_qx_up ) dqx_drho_down = dqx_drho_down - q0x_up *up /rho
|
|
|
|
dqc_drho_up = -4.0D0*pi/3.0D0 * (dqc_drho_up - ec)
|
|
dqc_drho_down = -4.0D0*pi/3.0D0 * (dqc_drho_down - ec)
|
|
|
|
dq0_drho_up (i_grid) = dq0_dq * (dqc_drho_up + dqx_drho_up )
|
|
dq0_drho_down(i_grid) = dq0_dq * (dqc_drho_down + dqx_drho_down)
|
|
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the theta functions of SOLER equation 8. These are
|
|
! defined as
|
|
!
|
|
! rho * P_i(q0(rho, grad_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 5 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 8 of SOLER). Start by interpolating the
|
|
! P_i polynomials defined in equation 3 in SOLER for the particular q0
|
|
! values we have.
|
|
|
|
CALL spline_interpolation (q_mesh, q0, thetas)
|
|
|
|
DO i_grid = 1, dfftp%nnr
|
|
thetas(i_grid,:) = thetas(i_grid,:) * total_rho(i_grid)
|
|
END DO
|
|
|
|
DO idx = 1, Nqs
|
|
CALL fwfft ('Rho', thetas(:,idx), dfftp)
|
|
END Do
|
|
|
|
END SUBROUTINE get_q0_on_grid_spin
|
|
|
|
|
|
!-----------------------------------------------------------------------
|
|
SUBROUTINE pw_spin( rs, zeta, ec, vc_up, vc_dw )
|
|
!-----------------------------------------------------------------------
|
|
!--A provisional copy of the pw routine in XC lib to avoid external calls--
|
|
!! J.P. Perdew and Y. Wang, PRB 45, 13244 (1992).
|
|
IMPLICIT NONE
|
|
REAL(DP), INTENT(IN) :: rs
|
|
!! Wigner-Seitz radius
|
|
REAL(DP), INTENT(IN) :: zeta
|
|
!! zeta = (rho_up - rho_dw)/rho_tot
|
|
REAL(DP), INTENT(OUT) :: ec, vc_up, vc_dw
|
|
!
|
|
REAL(DP) :: rs12, rs32, rs2, zeta2, zeta3, zeta4, fz, dfz
|
|
REAL(DP) :: om, dom, olog, epwc, vpwc
|
|
REAL(DP) :: omp, domp, ologp, epwcp, vpwcp
|
|
REAL(DP) :: oma, doma, ologa, alpha, vpwca
|
|
!
|
|
! xc parameters, unpolarised
|
|
REAL(DP), PARAMETER :: a = 0.031091d0, a1 = 0.21370d0, b1 = 7.5957d0, b2 = &
|
|
3.5876d0, b3 = 1.6382d0, b4 = 0.49294d0, c0 = a, c1 = 0.046644d0, &
|
|
c2 = 0.00664d0, c3 = 0.01043d0, d0 = 0.4335d0, d1 = 1.4408d0
|
|
! xc parameters, polarised
|
|
REAL(DP), PARAMETER :: ap = 0.015545d0, a1p = 0.20548d0, b1p = 14.1189d0, b2p &
|
|
= 6.1977d0, b3p = 3.3662d0, b4p = 0.62517d0, c0p = ap, c1p = &
|
|
0.025599d0, c2p = 0.00319d0, c3p = 0.00384d0, d0p = 0.3287d0, d1p &
|
|
= 1.7697d0
|
|
! xc PARAMETERs, antiferro
|
|
REAL(DP), PARAMETER :: aa = 0.016887d0, a1a = 0.11125d0, b1a = 10.357d0, b2a = &
|
|
3.6231d0, b3a = 0.88026d0, b4a = 0.49671d0, c0a = aa, c1a = &
|
|
0.035475d0, c2a = 0.00188d0, c3a = 0.00521d0, d0a = 0.2240d0, d1a &
|
|
= 0.3969d0
|
|
REAL(DP), PARAMETER :: fz0 = 1.709921d0
|
|
!
|
|
! if (rs < 0.5d0) then
|
|
! high density formula (not implemented)
|
|
!
|
|
! elseif (rs > 100.d0) then
|
|
! low density formula (not implemented)
|
|
!
|
|
! else
|
|
! interpolation formula
|
|
!
|
|
zeta2 = zeta * zeta
|
|
zeta3 = zeta2 * zeta
|
|
zeta4 = zeta3 * zeta
|
|
rs12 = SQRT(rs)
|
|
rs32 = rs * rs12
|
|
rs2 = rs**2
|
|
! unpolarised
|
|
om = 2.d0 * a * (b1 * rs12 + b2 * rs + b3 * rs32 + b4 * rs2)
|
|
dom = 2.d0 * a * (0.5d0 * b1 * rs12 + b2 * rs + 1.5d0 * b3 * rs32 &
|
|
+ 2.d0 * b4 * rs2)
|
|
olog = LOG(1.d0 + 1.0d0 / om)
|
|
epwc = - 2.d0 * a * (1.d0 + a1 * rs) * olog
|
|
vpwc = - 2.d0 * a * (1.d0 + 2.d0 / 3.d0 * a1 * rs) * olog - 2.d0 / &
|
|
3.d0 * a * (1.d0 + a1 * rs) * dom / (om * (om + 1.d0) )
|
|
! polarized
|
|
omp = 2.d0 * ap * (b1p * rs12 + b2p * rs + b3p * rs32 + b4p * rs2)
|
|
domp = 2.d0 * ap * (0.5d0 * b1p * rs12 + b2p * rs + 1.5d0 * b3p * &
|
|
rs32 + 2.d0 * b4p * rs2)
|
|
ologp = LOG(1.d0 + 1.0d0 / omp)
|
|
epwcp = - 2.d0 * ap * (1.d0 + a1p * rs) * ologp
|
|
vpwcp = - 2.d0 * ap * (1.d0 + 2.d0 / 3.d0 * a1p * rs) * ologp - &
|
|
2.d0 / 3.d0 * ap * (1.d0 + a1p * rs) * domp / (omp * (omp + 1.d0))
|
|
! antiferro
|
|
oma = 2.d0 * aa * (b1a * rs12 + b2a * rs + b3a * rs32 + b4a * rs2)
|
|
doma = 2.d0 * aa * ( 0.5d0 * b1a * rs12 + b2a * rs + 1.5d0 * b3a * &
|
|
rs32 + 2.d0 * b4a * rs2 )
|
|
ologa = LOG( 1.d0 + 1.0d0/oma )
|
|
alpha = 2.d0 * aa * (1.d0 + a1a*rs) * ologa
|
|
vpwca = + 2.d0 * aa * (1.d0 + 2.d0/3.d0 * a1a * rs) * ologa + &
|
|
2.d0 / 3.d0 * aa * (1.d0 + a1a*rs) * doma / (oma * (oma + 1.d0))
|
|
!
|
|
fz = ( (1.d0 + zeta)**(4.d0 / 3.d0) + (1.d0 - zeta)**(4.d0 / &
|
|
3.d0) - 2.d0) / (2.d0** (4.d0 / 3.d0) - 2.d0)
|
|
dfz = ( (1.d0 + zeta)**(1.d0 / 3.d0) - (1.d0 - zeta)**(1.d0 / &
|
|
3.d0) ) * 4.d0 / (3.d0 * (2.d0** (4.d0 / 3.d0) - 2.d0) )
|
|
!
|
|
ec = epwc + alpha * fz * (1.d0 - zeta4) / fz0 + (epwcp - epwc) &
|
|
* fz * zeta4
|
|
vc_up = vpwc + vpwca * fz * (1.d0 - zeta4) / fz0 + (vpwcp - vpwc) &
|
|
* fz * zeta4 + (alpha / fz0 * (dfz * (1.d0 - zeta4) - 4.d0 * fz * &
|
|
zeta3) + (epwcp - epwc) * (dfz * zeta4 + 4.d0 * fz * zeta3) ) &
|
|
* (1.d0 - zeta)
|
|
vc_dw = vpwc + vpwca * fz * (1.d0 - zeta4) / fz0 + (vpwcp - vpwc) &
|
|
* fz * zeta4 - (alpha / fz0 * (dfz * (1.d0 - zeta4) - 4.d0 * fz * &
|
|
zeta3) + (epwcp - epwc) * (dfz * zeta4 + 4.d0 * fz * zeta3) ) &
|
|
* (1.d0 + zeta)
|
|
RETURN
|
|
!
|
|
END SUBROUTINE pw_spin
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | saturate_q |
|
|
! |______________|
|
|
|
|
SUBROUTINE saturate_q (q, q_cutoff, q0, dq0_dq)
|
|
|
|
!! Here, we calculate \(\text{q0}\) by saturating \(\text{q}\) according to
|
|
!! equation 5 of SOLER. Also, we find the derivative \(\text{dq0/dq}\) needed
|
|
!! for the derivatives \(\text{dq0/drho}\) and \(\text{dq0/dgradrh0}\).
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), INTENT(IN) :: q ! Input q.
|
|
REAL(DP), INTENT(IN) :: q_cutoff ! Cutoff q.
|
|
REAL(DP), INTENT(OUT) :: q0 ! Output saturated q.
|
|
REAL(DP), INTENT(OUT) :: dq0_dq ! Derivative of dq0/dq.
|
|
|
|
REAL(DP) :: e_exp ! Exponent.
|
|
INTEGER, PARAMETER :: m_cut = 12 ! How many terms to include in
|
|
! the sum of SOLER equation 5.
|
|
! --------------------------------------------------------------------
|
|
|
|
e_exp = 0.0D0
|
|
dq0_dq = 0.0D0
|
|
|
|
DO idx = 1, m_cut
|
|
e_exp = e_exp + (q/q_cutoff)**idx/idx
|
|
dq0_dq = dq0_dq + (q/q_cutoff)**(idx-1)
|
|
END Do
|
|
|
|
q0 = q_cutoff*(1.0D0 - EXP(-e_exp))
|
|
dq0_dq = dq0_dq * EXP(-e_exp)
|
|
|
|
END SUBROUTINE saturate_q
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | vdW_DF_energy |
|
|
! |_______________|
|
|
!
|
|
!
|
|
SUBROUTINE vdW_DF_energy (thetas, vdW_xc_energy)
|
|
|
|
!! This routine carries out the integration of equation 8 of SOLER. It
|
|
!! returns the non-local exchange-correlation energy and the \(\text{u_alpha(k)}\)
|
|
!! arrays used to find the \(\text{u_alpha(r)}\) arrays via equations 11 and 12 in
|
|
!! SOLER.
|
|
|
|
USE gvect, ONLY : gg, ngm, igtongl, gl, ngl, gstart
|
|
USE cell_base, ONLY : tpiba, omega
|
|
|
|
IMPLICIT NONE
|
|
|
|
COMPLEX(DP), INTENT(INOUT) :: thetas(:,:) ! On input this variable holds the theta
|
|
! functions (equation 8, 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.
|
|
|
|
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, i_grid ! Index variables.
|
|
|
|
COMPLEX(DP) :: theta(Nqs), thetam(Nqs), theta_g(Nqs) ! Temporary storage arrays 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).
|
|
|
|
|
|
|
|
|
|
ALLOCATE ( u_vdW(dfftp%nnr,Nqs), kernel_of_k(Nqs, Nqs) )
|
|
vdW_xc_energy = 0.0D0
|
|
u_vdW(:,:) = CMPLX(0.0_DP, 0.0_DP, kind=dp)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! 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
|
|
! 11. These are kept in thetas array. Here we should use gstart,ngm
|
|
! but all the cases are handled 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(dfftp%nl(g_i),:)
|
|
|
|
DO q2_i = 1, Nqs
|
|
DO q1_i = 1, Nqs
|
|
u_vdW(dfftp%nl(g_i),q2_i) = u_vdW(dfftp%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(dfftp%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(dfftp%nlm(:),:) = CONJG( u_vdW(dfftp%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
|
|
! being 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
|
|
|
|
thetas(:,:) = u_vdW(:,:)
|
|
DEALLOCATE ( u_vdW, kernel_of_k )
|
|
|
|
END SUBROUTINE vdW_DF_energy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | vdW_DF_potential |
|
|
! |___________________|
|
|
!
|
|
!
|
|
SUBROUTINE vdW_DF_potential (q0, dq0_drho, dq0_dgradrho, grad_rho, u_vdW, 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 10. The \(\text{u_alpha(k)}\) functions were found
|
|
!! while calculating the energy. They are passed in as the matrix \(\text{u_vdW}\).
|
|
!! Most of the required derivatives were calculated in the \(\texttt{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.
|
|
|
|
USE gvect, ONLY : g
|
|
USE cell_base, ONLY : alat, tpiba
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), INTENT(IN) :: q0(:), grad_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 11 and 12 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 ! Intermediate 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) :: gradient2 ! Squared gradient.
|
|
|
|
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('vdW_DF_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
|
|
|
|
P = a*y(q_low) + b*y(q_hi) + c*d2y_dx2(P_i,q_low) + d*d2y_dx2(P_i,q_hi)
|
|
dP_dq0 = (y(q_hi) - y(q_low))/dq - e*d2y_dx2(P_i,q_low) + f*d2y_dx2(P_i,q_hi)
|
|
|
|
|
|
! --------------------------------------------------------------
|
|
! The first term in equation 10 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(:) * grad_rho(icar,:), 0.0_DP, kind=dp )
|
|
|
|
DO i_grid = 1, dfftp%nnr
|
|
gradient2 = grad_rho(1,i_grid)**2 + grad_rho(2,i_grid)**2 + grad_rho(3,i_grid)**2
|
|
IF ( gradient2 > 0.0D0 ) h(i_grid) = h(i_grid) / SQRT( gradient2 )
|
|
END DO
|
|
|
|
CALL fwfft ('Rho', h, dfftp)
|
|
h(dfftp%nl(:)) = CMPLX(0.0_DP,1.0_DP, kind=dp) * tpiba * g(icar,:) * h(dfftp%nl(:))
|
|
IF (gamma_only) h(dfftp%nlm(:)) = CONJG(h(dfftp%nl(:)))
|
|
CALL invfft ('Rho', h, dfftp)
|
|
potential(:) = potential(:) - REAL(h(:))
|
|
|
|
END Do
|
|
|
|
DEALLOCATE ( h_prefactor, h )
|
|
|
|
END SUBROUTINE vdW_DF_potential
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | SPLINE_INTERPOLATION |
|
|
! |________________________|
|
|
!
|
|
!
|
|
SUBROUTINE spline_interpolation (x, evaluation_points, values)
|
|
|
|
!! This routine is modeled after an algorithm from NUMERICAL_RECIPES 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.
|
|
|
|
IMPLICIT NONE
|
|
|
|
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, P_i ! Some indexing variables.
|
|
|
|
REAL(DP), ALLOCATABLE :: y(:) ! Temporary variables needed for the
|
|
REAL(DP) :: a, b, c, d, dx ! interpolation.
|
|
|
|
|
|
|
|
|
|
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 )
|
|
|
|
idx = (upper_bound+lower_bound) / 2
|
|
|
|
IF ( evaluation_points(i_grid) > x(idx) ) THEN
|
|
lower_bound = idx
|
|
ELSE
|
|
upper_bound = idx
|
|
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 |
|
|
! |___________________________________|
|
|
!
|
|
|
|
SUBROUTINE initialize_spline_interpolation (x, d2y_dx2)
|
|
|
|
!! This routine is modeled after an algorithm from NUMERICAL RECIPES It
|
|
!! was adapted for Fortran and for the problem at hand.
|
|
|
|
IMPLICIT NONE
|
|
|
|
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 ! 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 idx = 2, Nx-1
|
|
|
|
temp1 = (x(idx)-x(idx-1))/(x(idx+1)-x(idx-1))
|
|
temp2 = temp1 * d2y_dx2(P_i,idx-1) + 2.0D0
|
|
d2y_dx2(P_i,idx) = (temp1-1.0D0)/temp2
|
|
|
|
temp_array(idx) = (y(idx+1)-y(idx))/(x(idx+1)-x(idx)) &
|
|
- (y(idx)-y(idx-1))/(x(idx)-x(idx-1))
|
|
temp_array(idx) = (6.0D0*temp_array(idx)/(x(idx+1)-x(idx-1)) &
|
|
- temp1*temp_array(idx-1))/temp2
|
|
|
|
END DO
|
|
|
|
d2y_dx2(P_i,Nx) = 0.0D0
|
|
|
|
DO idx=Nx-1, 1, -1
|
|
|
|
d2y_dx2(P_i,idx) = d2y_dx2(P_i,idx) * d2y_dx2(P_i,idx+1) + temp_array(idx)
|
|
|
|
END DO
|
|
|
|
END DO
|
|
|
|
DEALLOCATE( temp_array, y )
|
|
|
|
END SUBROUTINE initialize_spline_interpolation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | INTERPOLATE_KERNEL |
|
|
! |____________________|
|
|
!
|
|
|
|
SUBROUTINE interpolate_kernel (k, kernel_of_k)
|
|
|
|
!! This routine is modeled after an algorithm from NUMERICAL RECIPES
|
|
!! Adapted for Fortran and the problem at hand. This function is used
|
|
!! to find the Phi-alpha-beta needed for equations 8 and 11 of SOLER.
|
|
|
|
IMPLICIT NONE
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | VDW_DF_STRESS |
|
|
! |_________________|
|
|
|
|
SUBROUTINE vdW_DF_stress (rho_valence, rho_core, nspin, sigma) ! PH adjusted for wrapper spin/nospin
|
|
|
|
!! vdW-DF stress calculation.
|
|
|
|
use gvect, ONLY : ngm, g
|
|
USE cell_base, ONLY : tpiba
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), INTENT(IN) :: rho_valence(:,:) !
|
|
REAL(dp), INTENT(IN) :: rho_core(:) ! Input variables.
|
|
INTEGER, INTENT(IN) :: nspin !
|
|
REAL(dp), INTENT(INOUT) :: sigma(3,3) !
|
|
|
|
REAL(DP), ALLOCATABLE :: grad_rho(:,:) !
|
|
REAL(DP), ALLOCATABLE :: total_rho(:) ! Rho values.
|
|
|
|
real(dp), allocatable :: rho_up(:) !
|
|
real(dp), allocatable :: rho_down(:) !
|
|
|
|
real(dp), allocatable :: grad_rho_up(:,:) ! The gradient of the up charge density.
|
|
! Same format as grad_rho
|
|
real(dp), allocatable :: grad_rho_down(:,:) ! The gradient of the down charge density.
|
|
! Same format as grad_rho
|
|
|
|
|
|
|
|
REAL(DP), ALLOCATABLE :: q0(:) !
|
|
REAL(DP), ALLOCATABLE :: dq0_drho(:) ! q-values.
|
|
REAL(DP), ALLOCATABLE :: dq0_dgradrho(:) !
|
|
real(dp), allocatable :: dq0_drho_up(:) ! The derivative of the saturated q0
|
|
real(dp), allocatable :: dq0_drho_down(:) ! with respect to the spin charge density
|
|
real(dp), allocatable :: dq0_dgradrho_up(:) ! The derivative of the saturated q0
|
|
real(dp), allocatable :: dq0_dgradrho_down(:) ! with respect to the gradient of the spin charge sensity
|
|
|
|
COMPLEX(DP), ALLOCATABLE :: thetas(:,:) ! Thetas.
|
|
INTEGER :: i_proc, theta_i, l, m
|
|
|
|
REAL(DP) :: sigma_grad(3,3)
|
|
REAL(DP) :: sigma_ker(3,3)
|
|
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Tests
|
|
|
|
IF ( inlc > 5 ) CALL errore( 'xc_vdW_DF', 'inlc not implemented', 1 )
|
|
|
|
#if defined (__SPIN_BALANCED)
|
|
IF ( nspin==2 ) THEN
|
|
WRITE(stdout,'(/,/ " Performing spin-balanced Ecnl stress calculation!")')
|
|
ELSE IF ( nspin > 2 ) THEN
|
|
CALL errore ('vdW_DF_stress', 'noncollinear vdW stress not implemented', 1)
|
|
END IF
|
|
#else
|
|
IF ( nspin>2 ) THEN
|
|
CALL errore ('vdW_DF_stress', 'vdW stress not implemented for nspin > 2', 1)
|
|
END IF
|
|
#endif
|
|
|
|
sigma(:,:) = 0.0_DP
|
|
sigma_grad(:,:) = 0.0_DP
|
|
sigma_ker(:,:) = 0.0_DP
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Allocations
|
|
|
|
ALLOCATE( total_rho(dfftp%nnr), grad_rho(3,dfftp%nnr), thetas(dfftp%nnr, Nqs), q0(dfftp%nnr) )
|
|
ALLOCATE ( dq0_drho(dfftp%nnr), dq0_dgradrho(dfftp%nnr) )
|
|
#if defined (__SPIN_BALANCED)
|
|
#else
|
|
IF (nspin==2) THEN
|
|
allocate( rho_up(dfftp%nnr), rho_down(dfftp%nnr) )
|
|
allocate( grad_rho_up(3,dfftp%nnr), grad_rho_down(3,dfftp%nnr) )
|
|
allocate( dq0_drho_up (dfftp%nnr), dq0_dgradrho_up (dfftp%nnr) )
|
|
allocate( dq0_drho_down(dfftp%nnr), dq0_dgradrho_down(dfftp%nnr) )
|
|
ENDIF
|
|
#endif
|
|
|
|
! --------------------------------------------------------------------
|
|
! Charge
|
|
|
|
total_rho = rho_valence(:,1) + rho_core(:)
|
|
#if defined (__SPIN_BALANCED)
|
|
#else
|
|
IF (nspin==2) THEN
|
|
rho_up = ( rho_valence(:,1) + rho_valence(:,2) + rho_core(:) )*0.5D0
|
|
rho_down = ( rho_valence(:,1) - rho_valence(:,2) + rho_core(:) )*0.5D0
|
|
ENDIF
|
|
#endif
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Here we calculate the gradient in reciprocal space using FFT.
|
|
|
|
CALL fft_gradient_r2r (dfftp, total_rho, g, grad_rho)
|
|
#if defined (__SPIN_BALANCED)
|
|
#else
|
|
IF (nspin==2) THEN
|
|
call fft_gradient_r2r (dfftp, rho_up, g, grad_rho_up)
|
|
call fft_gradient_r2r (dfftp, rho_down, g, grad_rho_down)
|
|
ENDIF
|
|
#endif
|
|
|
|
! --------------------------------------------------------------------
|
|
! Get q0.
|
|
|
|
#if defined (__SPIN_BALANCED)
|
|
CALL get_q0_on_grid (total_rho, grad_rho, q0, dq0_drho, dq0_dgradrho, thetas)
|
|
#else
|
|
IF (nspin == 1) THEN
|
|
CALL get_q0_on_grid (total_rho, grad_rho, q0, dq0_drho, dq0_dgradrho, thetas)
|
|
ELSEIF (nspin==2) THEN
|
|
CALL get_q0_on_grid_spin ( total_rho, rho_up, rho_down, grad_rho, grad_rho_up, grad_rho_down, &
|
|
q0, dq0_drho_up, dq0_drho_down, dq0_dgradrho_up, dq0_dgradrho_down, thetas)
|
|
ENDIF
|
|
#endif
|
|
|
|
! --------------------------------------------------------------------
|
|
! Stress
|
|
|
|
#if defined (__SPIN_BALANCED)
|
|
CALL vdW_DF_stress_gradient (total_rho, grad_rho, q0, dq0_drho, dq0_dgradrho, &
|
|
thetas, sigma_grad)
|
|
#else
|
|
IF (nspin == 1) THEN
|
|
CALL vdW_DF_stress_gradient (total_rho, grad_rho, q0, dq0_drho, dq0_dgradrho, &
|
|
thetas, sigma_grad)
|
|
ELSEIF (nspin == 2) THEN
|
|
CALL vdW_DF_stress_gradient_spin (total_rho, grad_rho_up, grad_rho_down, q0, &
|
|
dq0_dgradrho_up, dq0_dgradrho_down, &
|
|
thetas, sigma_grad)
|
|
ENDIF
|
|
#endif
|
|
|
|
|
|
CALL vdW_DF_stress_kernel (total_rho, q0, thetas, sigma_ker)
|
|
|
|
sigma = - (sigma_grad + sigma_ker)
|
|
|
|
DO l = 1, 3
|
|
DO m = 1, l - 1
|
|
sigma (m, l) = sigma (l, m)
|
|
END DO
|
|
END DO
|
|
|
|
DEALLOCATE( total_rho, grad_rho, thetas, q0 )
|
|
DEALLOCATE( dq0_drho, dq0_dgradrho )
|
|
#if defined (__SPIN_BALANCED)
|
|
#else
|
|
IF (nspin == 2) THEN
|
|
deallocate( rho_up, rho_down )
|
|
deallocate( grad_rho_up, grad_rho_down )
|
|
deallocate( dq0_drho_up, dq0_drho_down, dq0_dgradrho_up, dq0_dgradrho_down )
|
|
ENDIF
|
|
#endif
|
|
|
|
END SUBROUTINE vdW_DF_stress ! PH adjusted for wrapper spin/nospin
|
|
|
|
|
|
! -------------------------------------------------------------------------
|
|
! Begin Spin vdW-DF_strees_gradient implemented Per Hyldgaard 2019, GPL. No Waranties
|
|
! Adapted from the original nspin = 1 code (subroutine below) by Thonhauser and coauthors
|
|
! -------------------------------------------------------------------------
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | VDW_DF_STRESS_GRADIENT_SPIN |
|
|
! |_______________________________|
|
|
|
|
SUBROUTINE vdW_DF_stress_gradient_spin (total_rho, grad_rho_up, grad_rho_down, q0, &
|
|
dq0_dgradrho_up, dq0_dgradrho_down, &
|
|
thetas, sigma)
|
|
|
|
USE gvect, ONLY : ngm, g, gg, gstart
|
|
USE cell_base, ONLY : omega, tpiba, alat, at, tpiba2
|
|
|
|
implicit none
|
|
|
|
real(dp), intent(IN) :: total_rho(:) !
|
|
real(dp), intent(IN) :: grad_rho_up (:, :) ! Input variables.
|
|
real(dp), intent(IN) :: grad_rho_down(:, :) !
|
|
real(dp), intent(inout) :: sigma(:,:) !
|
|
real(dp), intent(IN) :: q0(:) !
|
|
real(dp), intent(IN) :: dq0_dgradrho_up(:) !
|
|
real(dp), intent(IN) :: dq0_dgradrho_down(:) !
|
|
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_up, prefactor_down ! Final summation of sigma.
|
|
real(dp) :: grad2_up, grad2_down ! Magnitude of density gradient.
|
|
|
|
|
|
integer :: i_proc, theta_i, i_grid, q_i, & !
|
|
ix, iy, iz ! Iterators.
|
|
|
|
character(LEN=1) :: intvar
|
|
|
|
allocate( d2y_dx2(Nqs, Nqs) )
|
|
allocate( u_vdW(dfftp%nnr, Nqs) )
|
|
|
|
sigma(:,:) = 0.0_DP
|
|
prefactor_up = 0.0_DP
|
|
prefactor_down = 0.0_DP
|
|
|
|
! --------------------------------------------------------------------
|
|
! Get u in k-space.
|
|
|
|
call thetas_to_uk(thetas, u_vdW)
|
|
|
|
! --------------------------------------------------------------------
|
|
! Get u in real space.
|
|
|
|
do theta_i = 1, Nqs
|
|
CALL invfft('Rho', u_vdW(:,theta_i), dfftp)
|
|
end do
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Get the second derivatives for interpolating the P_i.
|
|
|
|
call initialize_spline_interpolation(q_mesh, d2y_dx2(:,:))
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Do the real space integration to obtain the stress component.
|
|
|
|
do i_grid = 1, dfftp%nnr
|
|
|
|
if ( total_rho(i_grid) < epsr ) cycle
|
|
|
|
q_low = 1
|
|
q_hi = Nqs
|
|
grad2_up = sqrt( grad_rho_up(1,i_grid)**2 &
|
|
+ grad_rho_up(2,i_grid)**2 + grad_rho_up(3,i_grid)**2 )
|
|
grad2_down = sqrt( grad_rho_down(1,i_grid)**2 &
|
|
+ grad_rho_down(2,i_grid)**2 + grad_rho_down(3,i_grid)**2 )
|
|
|
|
if ( grad2_up == 0.0_dp .or. grad2_down == 0.0_dp) cycle
|
|
|
|
! -----------------------------------------------------------------
|
|
! Figure out which bin our value of q0 is 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('vdW_DF_stress_gradient_spin','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_up = u_vdW(i_grid,q_i) * dP_dq0 * dq0_dgradrho_up(i_grid) / grad2_up
|
|
prefactor_down = u_vdW(i_grid,q_i) * dP_dq0 * dq0_dgradrho_down(i_grid) / grad2_down
|
|
|
|
do l = 1, 3
|
|
do m = 1, l
|
|
|
|
sigma (l, m) = sigma (l, m) - e2 * prefactor_up * &
|
|
(grad_rho_up(l,i_grid) * grad_rho_up(m,i_grid))
|
|
sigma (l, m) = sigma (l, m) - e2 * prefactor_down * &
|
|
(grad_rho_down(l,i_grid) * grad_rho_down(m,i_grid))
|
|
end do
|
|
end do
|
|
|
|
end do
|
|
|
|
end do
|
|
|
|
call mp_sum( sigma, intra_bgrp_comm )
|
|
|
|
call dscal (9, 1.d0 / (dfftp%nr1 * dfftp%nr2 * dfftp%nr3), sigma, 1)
|
|
|
|
deallocate( d2y_dx2, u_vdW )
|
|
|
|
END SUBROUTINE vdW_DF_stress_gradient_spin
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | VDW_DF_STRESS_GRADIENT |
|
|
! |__________________________|
|
|
|
|
SUBROUTINE vdW_DF_stress_gradient (total_rho, grad_rho, q0, &
|
|
dq0_drho, dq0_dgradrho, thetas, sigma)
|
|
|
|
USE gvect, ONLY : ngm, g, gstart
|
|
USE cell_base, ONLY : omega, tpiba, alat, at, tpiba2
|
|
|
|
IMPLICIT NONE
|
|
|
|
REAL(DP), INTENT(IN) :: total_rho(:) !
|
|
REAL(DP), INTENT(IN) :: grad_rho(:, :) !
|
|
REAL(DP), INTENT(INOUT) :: sigma(:,:) !
|
|
REAL(DP), INTENT(IN) :: q0(:) ! Input variables.
|
|
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
|
|
REAL(DP) :: grad2 ! magnitude of density gradient.
|
|
|
|
INTEGER :: i_proc, theta_i, i_grid, q_i, & ! Iterators.
|
|
ix, iy, iz !
|
|
|
|
CHARACTER(LEN=1) :: intvar
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
DO theta_i = 1, Nqs
|
|
CALL invfft('Rho', u_vdW(:,theta_i), dfftp)
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Get the second derivatives for interpolating the P_i.
|
|
|
|
CALL initialize_spline_interpolation(q_mesh, d2y_dx2(:,:))
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Do the real space integration to obtain the stress component.
|
|
|
|
DO i_grid = 1, dfftp%nnr
|
|
|
|
IF ( total_rho(i_grid) < epsr ) CYCLE
|
|
|
|
q_low = 1
|
|
q_hi = Nqs
|
|
grad2 = sqrt( grad_rho(1,i_grid)**2 + grad_rho(2,i_grid)**2 + grad_rho(3,i_grid)**2 )
|
|
|
|
IF ( grad2 == 0.0_dp ) CYCLE
|
|
|
|
! -----------------------------------------------------------------
|
|
! Figure out which bin our value of q0 is 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) / grad2
|
|
|
|
DO l = 1, 3
|
|
DO m = 1, l
|
|
|
|
sigma (l, m) = sigma (l, m) - e2 * prefactor * &
|
|
(grad_rho(l,i_grid) * grad_rho(m,i_grid))
|
|
END DO
|
|
END DO
|
|
|
|
END DO
|
|
|
|
END DO
|
|
|
|
CALL mp_sum( sigma, intra_bgrp_comm )
|
|
|
|
CALL dscal (9, 1.0D0 / (dfftp%nr1 * dfftp%nr2 * dfftp%nr3), sigma, 1)
|
|
|
|
DEALLOCATE( d2y_dx2, u_vdW )
|
|
|
|
END SUBROUTINE vdW_DF_stress_gradient
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | VDW_DF_STRESS_KERNEL |
|
|
! |________________________|
|
|
|
|
SUBROUTINE vdW_DF_stress_kernel (total_rho, q0, thetas, sigma)
|
|
|
|
USE gvect, ONLY : ngm, g, gg, igtongl, gl, ngl, gstart
|
|
USE cell_base, ONLY : omega, tpiba, tpiba2
|
|
|
|
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
|
|
INTEGER :: last_g, theta_i
|
|
REAL(DP) :: g2, ngmod2, g_kernel, G_multiplier
|
|
|
|
|
|
|
|
|
|
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(dfftp%nl(g_i),q1_i) * &
|
|
dkernel_of_dk(q1_i,q2_i)*conjg(thetas(dfftp%nl(g_i),q2_i))* &
|
|
(g (l, g_i) * g (m, g_i) * tpiba2) / g_kernel
|
|
END DO
|
|
END DO
|
|
END DO
|
|
END DO
|
|
|
|
IF ( g_i < gstart ) sigma(:,:) = sigma(:,:) / G_multiplier
|
|
|
|
END DO
|
|
|
|
CALL mp_sum( sigma, intra_bgrp_comm )
|
|
|
|
DEALLOCATE( dkernel_of_dk )
|
|
|
|
END SUBROUTINE vdW_DF_stress_kernel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | INTERPOLATE_DKERNEL_DK |
|
|
! |________________________|
|
|
|
|
SUBROUTINE interpolate_Dkernel_Dk (k, dkernel_of_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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | thetas_to_uk |
|
|
! |______________|
|
|
|
|
|
|
SUBROUTINE thetas_to_uk (thetas, u_vdW)
|
|
|
|
USE gvect, ONLY : gg, ngm, igtongl, gl, ngl, gstart
|
|
USE cell_base, ONLY : tpiba, omega
|
|
|
|
IMPLICIT NONE
|
|
|
|
COMPLEX(DP), INTENT(IN) :: thetas(:,:) ! On input this variable holds the theta functions
|
|
! (equation 8, 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, 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, kind=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(dfftp%nl(g_i),:)
|
|
|
|
DO q2_i = 1, Nqs
|
|
DO q1_i = 1, Nqs
|
|
u_vdW(dfftp%nl(g_i),q2_i) = u_vdW(dfftp%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(dfftp%nlm(:),:) = CONJG(u_vdW(dfftp%nl(:),:))
|
|
|
|
DEALLOCATE( kernel_of_k )
|
|
|
|
END SUBROUTINE thetas_to_uk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | GENERATE_KERNEL |
|
|
! |_________________|
|
|
!
|
|
! The original definition of the kernel function is given in DION
|
|
! equations 14-16. The Soler method makes the kernel function a
|
|
! function of only 1 variable (r) by first putting it in the form
|
|
! phi(q1*r, q2*r). Then, the q-dependence is removed by expanding the
|
|
! function in a special way (see SOLER equation 3). This yields a
|
|
! separate function for each pair of q points that is a function of r
|
|
! alone. There are (Nqs^2+Nqs)/2 unique functions, where Nqs is the
|
|
! number of q points used. In the Soler method, the kernel is first
|
|
! made in the form phi(d1, d2) but this is not done here. It was found
|
|
! that, with q's chosen judiciously ahead of time, the kernel and the
|
|
! second derivatives required for interpolation could be tabulated
|
|
! ahead of time for faster use of the vdW-DF functional. Through
|
|
! testing we found no need to soften the kernel and correct for this
|
|
! later (see SOLER eqations 6-7).
|
|
!
|
|
! The algorithm employed here is "embarrassingly parallel," meaning
|
|
! that it parallelizes very well up to (Nqs^2+Nqs)/2 processors,
|
|
! where, again, Nqs is the number of q points chosen. However,
|
|
! parallelization on this scale is unnecessary. In testing the code
|
|
! runs in under a minute on 16 Intel Xeon processors.
|
|
!
|
|
! IMPORTANT NOTICE: Results are very sensitive to compilation details.
|
|
! In particular, the usage of FMA (Fused Multiply-and-Add)
|
|
! instructions used by modern CPUs such as AMD Interlagos (Bulldozer)
|
|
! and Intel Ivy Bridge may affect quite heavily some components of the
|
|
! kernel (communication by Ake Sandberg, Umea University). In practice
|
|
! this should not be a problem, since most affected elements are the
|
|
! less relevant ones.
|
|
!
|
|
! Some of the algorithms here are somewhat modified versions of those
|
|
! found in:
|
|
!
|
|
! Numerical Recipes in C; William H. Press, Brian P. Flannery, Saul
|
|
! A. Teukolsky, and William T. Vetterling. Cambridge University
|
|
! Press (1988).
|
|
!
|
|
! hereafter referred to as NUMERICAL_RECIPES. The routines were
|
|
! translated to Fortran, of course and variable names are generally
|
|
! different.
|
|
!
|
|
! For the calculation of the kernel we have benefited from access to
|
|
! earlier vdW-DF implementation into PWscf and ABINIT, written by Timo
|
|
! Thonhauser, Valentino Cooper, and David Langreth. These codes, in
|
|
! turn, benefited from earlier codes written by Maxime Dion and Henrik
|
|
! Rydberg.
|
|
|
|
SUBROUTINE generate_kernel
|
|
|
|
IMPLICIT NONE
|
|
|
|
INTEGER :: a_i, b_i, q1_i, q2_i, r_i
|
|
! Indexing variables.
|
|
|
|
REAL(DP) :: weights( Nintegration_points )
|
|
! Array to hold dx values for the Gaussian-Legendre integration of the kernel.
|
|
|
|
REAL(DP) :: sin_a( Nintegration_points ), cos_a( Nintegration_points )
|
|
! Sine and cosine values of the aforementioned points a.
|
|
|
|
REAL(DP) :: d1, d2, d, integral
|
|
! Intermediate values.
|
|
|
|
! --------------------------------------------------------------------
|
|
! The following variables control the parallel environment.
|
|
|
|
INTEGER :: my_start_q, my_end_q, Ntotal
|
|
! Starting and ending q value for each processor, also the total
|
|
! number of calculations to do, i.e. (Nqs^2 + Nqs)/2.
|
|
|
|
REAL(DP), ALLOCATABLE :: phi(:,:), phi_deriv(:,:)
|
|
! Arrays to store the kernel functions and their second derivatives.
|
|
! They are stored as phi(radial_point, idx).
|
|
|
|
INTEGER, ALLOCATABLE :: indices(:,:), proc_indices(:,:)
|
|
! Indices holds the values of q1 and q2 as partitioned out to the
|
|
! processors. It is an Ntotal x 2 array stored as indices(index of
|
|
! point number, q1:q2). Proc_indices holds the section of the indices
|
|
! array that is assigned to each processor. This is a Nproc x 2
|
|
! array, stored as proc_indices(processor_number,
|
|
! starting_index:ending_index)
|
|
|
|
INTEGER :: Nper, Nextra, start_q, end_q
|
|
! Baseline number of jobs per processor, number of processors that
|
|
! get an extra job in case the number of jobs doesn't split evenly
|
|
! over the number of processors, starting index into the indices
|
|
! array, ending index into the indices array.
|
|
|
|
INTEGER :: nproc, mpime
|
|
! Number or procs, rank of current processor.
|
|
|
|
INTEGER :: proc_i, my_Nqs
|
|
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Start the timer.
|
|
|
|
CALL start_clock ( 'vdW_kernel' )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! The total number of phi_alpha_beta functions that have to be
|
|
! calculated.
|
|
|
|
Ntotal = (Nqs**2 + Nqs)/2
|
|
ALLOCATE ( indices(Ntotal, 2) )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! This part fills in the indices array. It just loops through the q1
|
|
! and q2 values and stores them. Sections of this array will be
|
|
! assigned to each of the processors later.
|
|
|
|
idx = 1
|
|
|
|
DO q1_i = 1, Nqs
|
|
DO q2_i = 1, q1_i
|
|
indices(idx, 1) = q1_i
|
|
indices(idx, 2) = q2_i
|
|
idx = idx + 1
|
|
END DO
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Figure out the baseline number of functions to be calculated by each
|
|
! processor and how many processors get one extra job.
|
|
|
|
nproc = mp_size( intra_image_comm )
|
|
mpime = mp_rank( intra_image_comm )
|
|
Nper = Ntotal/nproc
|
|
Nextra = MOD(Ntotal, nproc)
|
|
|
|
ALLOCATE( proc_indices(nproc, 2) )
|
|
|
|
start_q = 0
|
|
end_q = 0
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Loop over all the processors and figure out which section of the
|
|
! indices array each processor should do. All processors figure this
|
|
! out for every processor so there is no need to communicate results.
|
|
|
|
DO proc_i = 1, nproc
|
|
|
|
start_q = end_q + 1
|
|
end_q = start_q + (Nper - 1)
|
|
IF (proc_i <= Nextra) end_q = end_q + 1
|
|
|
|
! This is to prevent trouble if number of processors exceeds Ntotal.
|
|
IF ( proc_i > Ntotal ) THEN
|
|
start_q = Ntotal
|
|
end_q = Ntotal
|
|
END IF
|
|
|
|
IF ( proc_i == (mpime+1) ) THEN
|
|
my_start_q = start_q
|
|
my_end_q = end_q
|
|
END IF
|
|
|
|
proc_indices(proc_i, 1) = start_q
|
|
proc_indices(proc_i, 2) = end_q
|
|
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Store how many jobs are assigned to me.
|
|
|
|
my_Nqs = my_end_q - my_start_q + 1
|
|
ALLOCATE( phi( 0:Nr_points, my_Nqs ), phi_deriv( 0:Nr_points, my_Nqs ) )
|
|
|
|
phi = 0.0D0
|
|
phi_deriv = 0.0D0
|
|
kernel = 0.0D0
|
|
d2phi_dk2 = 0.0D0
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Find the integration points we are going to use in the
|
|
! Gaussian-Legendre integration.
|
|
|
|
CALL prep_gaussian_quadrature( weights )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Get a, a^2, sin(a), cos(a) and the weights for the Gaussian-Legendre
|
|
! integration.
|
|
|
|
DO a_i=1, Nintegration_points
|
|
a_points (a_i) = TAN( a_points(a_i) )
|
|
a_points2(a_i) = a_points(a_i)**2
|
|
weights(a_i) = weights(a_i)*( 1 + a_points2(a_i) )
|
|
cos_a(a_i) = COS( a_points(a_i) )
|
|
sin_a(a_i) = SIN( a_points(a_i) )
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Calculate the value of the W function defined in DION equation 16
|
|
! for each value of a and b.
|
|
|
|
DO a_i = 1, Nintegration_points
|
|
DO b_i = 1, Nintegration_points
|
|
W_ab(a_i, b_i) = 2.0D0 * weights(a_i)*weights(b_i) * ( &
|
|
(3.0D0-a_points2(a_i))*a_points(b_i) *sin_a(a_i)*cos_a(b_i) + &
|
|
(3.0D0-a_points2(b_i))*a_points(a_i) *cos_a(a_i)*sin_a(b_i) + &
|
|
(a_points2(a_i)+a_points2(b_i)-3.0D0)*sin_a(a_i)*sin_a(b_i) - &
|
|
3.0D0*a_points(a_i)*a_points(b_i)*cos_a(a_i)*cos_a(b_i) ) / &
|
|
(a_points(a_i)*a_points(b_i) )
|
|
END DO
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! vdW-DF analysis tool as described in PRB 97, 085115 (2018).
|
|
|
|
IF ( vdW_DF_analysis == 1 ) THEN
|
|
|
|
DO a_i = 1, Nintegration_points
|
|
DO b_i = 1, Nintegration_points
|
|
W_ab(a_i, b_i) = weights(a_i)*weights(b_i) * &
|
|
a_points(a_i)*a_points(b_i)*sin_a(a_i)*sin_a(b_i)
|
|
END DO
|
|
END DO
|
|
|
|
ELSE IF ( vdW_DF_analysis == 2 ) THEN
|
|
|
|
DO a_i = 1, Nintegration_points
|
|
DO b_i = 1, Nintegration_points
|
|
W_ab(a_i, b_i) = W_ab(a_i, b_i) - weights(a_i)*weights(b_i) * &
|
|
a_points(a_i)*a_points(b_i)*sin_a(a_i)*sin_a(b_i)
|
|
END DO
|
|
END DO
|
|
|
|
END IF
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Now, we loop over all the pairs (q1,q2) that are assigned to us and
|
|
! perform our calculations.
|
|
|
|
DO idx = 1, my_Nqs
|
|
|
|
! -----------------------------------------------------------------
|
|
! First, get the value of phi(q1*r, q2*r) for each r and the
|
|
! particular values of q1 and q2 we are using.
|
|
|
|
DO r_i = 1, Nr_points
|
|
d1 = q_mesh( indices(idx+my_start_q-1, 1) ) * dr * r_i
|
|
d2 = q_mesh( indices(idx+my_start_q-1, 2) ) * dr * r_i
|
|
phi(r_i, idx) = phi_value(d1, d2)
|
|
END DO
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Now, perform a radial FFT to turn our phi_alpha_beta(r) into
|
|
! phi_alpha_beta(k) needed for SOLER equation 8.
|
|
|
|
CALL radial_fft( phi(:,idx) )
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Determine the spline interpolation coefficients for the Fourier
|
|
! transformed kernel function.
|
|
|
|
CALL set_up_splines( phi(:, idx), phi_deriv(:, idx) )
|
|
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Finally, we collect the results after letting everybody catch up.
|
|
|
|
CALL mp_barrier( intra_image_comm )
|
|
|
|
DO proc_i = 0, nproc-1
|
|
|
|
IF ( proc_i >= Ntotal ) EXIT
|
|
|
|
CALL mp_get ( phi , phi , mpime, 0, proc_i, 0, intra_image_comm )
|
|
CALL mp_get ( phi_deriv, phi_deriv, mpime, 0, proc_i, 0, intra_image_comm )
|
|
|
|
IF ( mpime == 0 ) THEN
|
|
|
|
DO idx = proc_indices(proc_i+1,1), proc_indices(proc_i+1,2)
|
|
q1_i = indices(idx, 1)
|
|
q2_i = indices(idx, 2)
|
|
kernel (:, q1_i, q2_i) = phi (:, idx - proc_indices(proc_i+1,1) + 1)
|
|
d2phi_dk2 (:, q1_i, q2_i) = phi_deriv (:, idx - proc_indices(proc_i+1,1) + 1)
|
|
kernel (:, q2_i, q1_i) = kernel (:, q1_i, q2_i)
|
|
d2phi_dk2 (:, q2_i, q1_i) = d2phi_dk2 (:, q1_i, q2_i)
|
|
END DO
|
|
|
|
END IF
|
|
|
|
END DO
|
|
|
|
CALL mp_bcast ( kernel , 0, intra_image_comm )
|
|
CALL mp_bcast ( d2phi_dk2, 0, intra_image_comm )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Keep the lines below for testing and combatibility with the old
|
|
! kernel file reading/writing method.
|
|
!
|
|
! Writing the calculated kernel.
|
|
!
|
|
! IF ( ionode ) THEN
|
|
! WRITE(stdout,'(/ / A)') " vdW-DF kernel table calculated and written to file."
|
|
! OPEN(UNIT=21, FILE='kernel_table', STATUS='replace', FORM='formatted', ACTION='write')
|
|
! WRITE(21, '(2i5,f13.8)') Nqs, Nr_points
|
|
! WRITE(21, '(1p4e23.14)') r_max
|
|
! WRITE(21, '(1p4e23.14)') q_mesh
|
|
! DO q1_i = 1, Nqs
|
|
! DO q2_i = 1, q1_i
|
|
! WRITE(21, '(1p4e23.14)') kernel(:, q1_i, q2_i)
|
|
! END DO
|
|
! END DO
|
|
! DO q1_i = 1, Nqs
|
|
! DO q2_i = 1, q1_i
|
|
! WRITE(21, '(1p4e23.14)') d2phi_dk2(:, q1_i, q2_i)
|
|
! END DO
|
|
! END DO
|
|
! CLOSE (21)
|
|
! END IF
|
|
!
|
|
!
|
|
! Reading the kernel from an old kernel file.
|
|
!
|
|
! IF (ionode) WRITE(stdout,'(/ / A)') " vdW-DF kernel read from file."
|
|
! OPEN(UNIT=21, FILE='vdW_kernel_table', STATUS='old', FORM='formatted', ACTION='read')
|
|
! read(21, '(/ / / / / /)')
|
|
! DO q1_i = 1, Nqs
|
|
! DO q2_i = 1, q1_i
|
|
! READ(21, '(1p4e23.14)') kernel(:, q1_i, q2_i)
|
|
! kernel(:, q2_i, q1_i) = kernel(:, q1_i, q2_i)
|
|
! END DO
|
|
! END DO
|
|
! DO q1_i = 1, Nqs
|
|
! DO q2_i = 1, q1_i
|
|
! READ(21, '(1p4e23.14)') d2phi_dk2(:, q1_i, q2_i)
|
|
! d2phi_dk2(:, q2_i, q1_i) = d2phi_dk2(:, q1_i, q2_i)
|
|
! END DO
|
|
! END DO
|
|
! CLOSE (21)
|
|
|
|
|
|
DEALLOCATE( indices, proc_indices, phi, phi_deriv )
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Stop the timer.
|
|
|
|
CALL stop_clock ( 'vdW_kernel' )
|
|
|
|
END SUBROUTINE generate_kernel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | PREP_GAUSSIAN_QUADRATURE |
|
|
! |____________________________|
|
|
!
|
|
|
|
SUBROUTINE prep_gaussian_quadrature( weights )
|
|
|
|
!! Routine to calculate the points and weights for the
|
|
!! Gaussian-Legendre integration. This routine is modeled after the
|
|
!! routine GAULEG from NUMERICAL RECIPES.
|
|
|
|
REAL(DP), INTENT(INOUT) :: weights(:)
|
|
! The points and weights for the Gaussian-Legendre integration.
|
|
|
|
INTEGER :: Npoints
|
|
! The number of points we actually have to calculate. The rest will
|
|
! be obtained from symmetry.
|
|
|
|
REAL(DP) :: poly_1, poly_2, poly_3
|
|
! Temporary storage for Legendre polynomials.
|
|
|
|
INTEGER :: i_point, i_poly
|
|
! Indexing variables.
|
|
|
|
REAL(DP) :: root, dp_dx, last_root
|
|
! The value of the root of a given Legendre polynomial, the derivative
|
|
! of the polynomial at that root and the value of the root in the last
|
|
! iteration (to check for convergence of Newton's method).
|
|
|
|
real(dp) :: midpoint, length
|
|
! The middle of the x-range and the length to that point.
|
|
|
|
|
|
|
|
|
|
Npoints = (Nintegration_points + 1)/2
|
|
midpoint = 0.5D0 * ( ATAN(a_min) + ATAN(a_max) )
|
|
length = 0.5D0 * ( ATAN(a_max) - ATAN(a_min) )
|
|
|
|
DO i_point = 1, Npoints
|
|
! -----------------------------------------------------------------
|
|
! Make an initial guess for the root.
|
|
|
|
root = COS(DBLE(pi*(i_point - 0.25D0)/(Nintegration_points + 0.5D0)))
|
|
|
|
DO
|
|
! --------------------------------------------------------------
|
|
! Use the recurrence relations to find the desired polynomial,
|
|
! evaluated at the approximate root. See NUMERICAL_RECIPES.
|
|
|
|
poly_1 = 1.0D0
|
|
poly_2 = 0.0D0
|
|
|
|
DO i_poly = 1, Nintegration_points
|
|
|
|
poly_3 = poly_2
|
|
poly_2 = poly_1
|
|
poly_1 = ((2.0D0 * i_poly - 1.0D0)*root*poly_2 - (i_poly-1.0D0)*poly_3)/i_poly
|
|
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------
|
|
! Use the recurrence relations to find the desired polynomial.
|
|
! Find the derivative of the polynomial and use it in Newton's
|
|
! method to refine our guess for the root.
|
|
|
|
dp_dx = Nintegration_points * (root*poly_1 - poly_2)/(root**2 - 1.0D0)
|
|
|
|
last_root = root
|
|
root = last_root - poly_1/dp_dx
|
|
|
|
|
|
! --------------------------------------------------------------
|
|
! Check for convergence.
|
|
|
|
IF (abs(root - last_root) <= 1.0D-14) EXIT
|
|
|
|
END DO
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Fill in the array of evaluation points.
|
|
|
|
a_points(i_point) = midpoint - length*root
|
|
a_points(Nintegration_points + 1 - i_point) = midpoint + length*root
|
|
|
|
|
|
! -----------------------------------------------------------------
|
|
! Fill in the array of weights.
|
|
|
|
weights(i_point) = 2.0D0 * length/((1.0D0 - root**2)*dp_dx**2)
|
|
weights(Nintegration_points + 1 - i_point) = weights(i_point)
|
|
|
|
END DO
|
|
|
|
END SUBROUTINE prep_gaussian_quadrature
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | PHI_VALUE |
|
|
! |_____________|
|
|
!
|
|
|
|
REAL(DP) FUNCTION phi_value(d1, d2)
|
|
|
|
!! This function returns the value of the kernel calculated via DION
|
|
!! equation 14.
|
|
|
|
REAL(DP), INTENT(IN) :: d1, d2
|
|
! The point at which to evaluate the kernel. d1 = q1*r and d2 = q2*r.
|
|
|
|
REAL(DP) :: w, x, y, z, T
|
|
! Intermediate values.
|
|
|
|
REAL(DP) :: nu(Nintegration_points), nu1(Nintegration_points)
|
|
! Defined in the discussio below equation 16 of DION.
|
|
|
|
INTEGER :: a_i, b_i
|
|
! Indexing variables.
|
|
|
|
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Loop over all integration points and calculate the value of the nu
|
|
! functions defined in the discussion below equation 16 in DION.
|
|
|
|
DO a_i = 1, Nintegration_points
|
|
nu(a_i) = a_points2(a_i)/( 2.0D0 * h_function( a_points(a_i)/d1 ))
|
|
nu1(a_i) = a_points2(a_i)/( 2.0D0 * h_function( a_points(a_i)/d2 ))
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Carry out the integration of DION equation 13.
|
|
|
|
phi_value = 0.0D0
|
|
|
|
DO a_i = 1, Nintegration_points
|
|
w = nu(a_i)
|
|
y = nu1(a_i)
|
|
DO b_i = 1, Nintegration_points
|
|
x = nu(b_i)
|
|
z = nu1(b_i)
|
|
T = (1.0D0/(w+x) + 1.0D0/(y+z))*(1.0D0/((w+y)*(x+z)) + 1.0D0/((w+z)*(y+x)))
|
|
phi_value = phi_value + T * W_ab(a_i, b_i)
|
|
END DO
|
|
END DO
|
|
|
|
phi_value = 1.0D0/pi**2*phi_value
|
|
|
|
END FUNCTION phi_value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | RADIAL_FFT |
|
|
! |______________|
|
|
!
|
|
|
|
SUBROUTINE radial_fft(phi)
|
|
|
|
!! This subroutine performs a radial Fourier transform on the
|
|
!! real-space kernel functions.
|
|
! Basically, this is just:
|
|
! int(4*pi*r^2*phi*sin(k*r)/(k*r))dr
|
|
! integrated from 0 to r_max.
|
|
! That is, it is the kernel function phi integrated with the 0^th spherical
|
|
! Bessel function radially, with a 4*pi assumed from angular
|
|
! integration since we have spherical symmetry. The spherical symmetry
|
|
! comes in because the kernel function depends only on the magnitude
|
|
! of the vector between two points. The integration is done using the
|
|
! trapezoid rule.
|
|
|
|
REAL(DP), INTENT(INOUT) :: phi(0:Nr_points)
|
|
! On input holds the real-space function phi_q1_q2(r).
|
|
! On output hold the reciprocal-space function phi_q1_q2(k).
|
|
|
|
REAL(DP) :: phi_k(0:Nr_points)
|
|
! Temporary storage for phi_q1_q2(k).
|
|
|
|
INTEGER :: k_i, r_i
|
|
! Indexing variables.
|
|
|
|
REAL(DP) :: r, k
|
|
! The real and reciprocal space points.
|
|
|
|
|
|
|
|
|
|
phi_k = 0.0D0
|
|
|
|
! --------------------------------------------------------------------
|
|
! Handle the k=0 point separately.
|
|
|
|
DO r_i = 1, Nr_points
|
|
r = r_i * dr
|
|
phi_k(0) = phi_k(0) + phi(r_i)*r**2
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Subtract half of the last value off because of the trapezoid rule.
|
|
|
|
phi_k(0) = phi_k(0) - 0.5D0 * (Nr_points*dr)**2 * phi(Nr_points)
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Integration for the rest of the k-points.
|
|
|
|
DO k_i = 1, Nr_points
|
|
k = k_i * dk
|
|
DO r_i = 1, Nr_points
|
|
r = r_i * dr
|
|
phi_k(k_i) = phi_k(k_i) + phi(r_i) * r * SIN(k*r) / k
|
|
END DO
|
|
phi_k(k_i) = phi_k(k_i) - 0.5D0 * phi(Nr_points) * r * SIN(k*r) / k
|
|
END DO
|
|
|
|
|
|
! --------------------------------------------------------------------
|
|
! Add in the 4*pi and the dr factor for the integration.
|
|
|
|
phi = 4.0D0 * pi * phi_k * dr
|
|
|
|
END SUBROUTINE radial_fft
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | SET UP SPLINES |
|
|
! |__________________|
|
|
!
|
|
|
|
SUBROUTINE set_up_splines(phi, D2)
|
|
|
|
!! This subroutine accepts a function (phi) and finds at each point the
|
|
!! second derivative (D2) for use with spline interpolation. This
|
|
!! function assumes we are using the expansion described in SOLER
|
|
!! equation 3.
|
|
! That is, the derivatives are those needed to interpolate
|
|
! Kronecker delta functions at each of the q values. Other than some
|
|
! special modification to speed up the algorithm in our particular
|
|
! case, this algorithm is taken directly from NUMERICAL_RECIPES.
|
|
|
|
REAL(DP), INTENT(IN) :: phi(0:Nr_points)
|
|
! The k-space kernel function for a particular q1 and q2.
|
|
|
|
REAL(DP), INTENT(INOUT) :: D2(0:Nr_points)
|
|
! The second derivatives to be used in the interpolation expansion
|
|
! (SOLER equation 3).
|
|
|
|
REAL(DP), ALLOCATABLE :: temp_array(:) ! Temporary storage.
|
|
REAL(DP) :: temp_1, temp_2
|
|
|
|
INTEGER :: r_i
|
|
! Indexing variable.
|
|
|
|
|
|
|
|
|
|
ALLOCATE( temp_array(0:Nr_points) )
|
|
|
|
D2 = 0
|
|
temp_array = 0
|
|
|
|
DO r_i = 1, Nr_points - 1
|
|
temp_1 = DBLE(r_i - (r_i - 1))/DBLE( (r_i + 1) - (r_i - 1) )
|
|
temp_2 = temp_1 * D2(r_i-1) + 2.0D0
|
|
D2(r_i) = (temp_1 - 1.0D0)/temp_2
|
|
temp_array(r_i) = ( phi(r_i+1) - phi(r_i))/DBLE( dk*((r_i+1) - r_i) ) - &
|
|
( phi(r_i) - phi(r_i-1))/DBLE( dk*(r_i - (r_i-1)) )
|
|
temp_array(r_i) = (6.0D0*temp_array(r_i)/DBLE( dk*((r_i+1) - (r_i-1)) )-&
|
|
temp_1*temp_array(r_i-1))/temp_2
|
|
END DO
|
|
|
|
D2(Nr_points) = 0.0D0
|
|
DO r_i = Nr_points-1, 0, -1
|
|
D2(r_i) = D2(r_i)*D2(r_i+1) + temp_array(r_i)
|
|
END DO
|
|
|
|
DEALLOCATE( temp_array )
|
|
|
|
END SUBROUTINE set_up_splines
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
! ####################################################################
|
|
! | |
|
|
! | VDW_INFO |
|
|
! |____________|
|
|
|
|
SUBROUTINE vdW_info( nspin )
|
|
|
|
USE xc_lib, ONLY : xclib_dft_is
|
|
|
|
IMPLICIT NONE
|
|
INTEGER, INTENT (IN) :: nspin
|
|
|
|
|
|
|
|
|
|
WRITE(stdout,'(/)')
|
|
WRITE(stdout,'(5x,"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% You are using vdW-DF, which was implemented by the Thonhauser group. %")')
|
|
WRITE(stdout,'(5x,"% Please cite the following two papers that made this development %")')
|
|
WRITE(stdout,'(5x,"% possible and the two reviews that describe the various versions: %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% T. Thonhauser et al., PRL 115, 136402 (2015). %")')
|
|
WRITE(stdout,'(5x,"% T. Thonhauser et al., PRB 76, 125112 (2007). %")')
|
|
WRITE(stdout,'(5x,"% K. Berland et al., Rep. Prog. Phys. 78, 066501 (2015). %")')
|
|
WRITE(stdout,'(5x,"% D.C. Langreth et al., J. Phys.: Condens. Matter 21, 084203 (2009). %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% If you are calculating stress with vdW-DF, please also cite: %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% R. Sabatini et al., J. Phys.: Condens. Matter 24, 424209 (2012). %")')
|
|
IF (nspin > 1) WRITE(stdout,'(5x,"% C.M. Frostenson et al., Electr. Struct. 4, 014001 (2022). %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%",/)')
|
|
WRITE(stdout,'(5x,"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% vdW-DF NEWS: %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% * vdW-DF3 is now available. DOI: 10.1021/acs.jctc.0c00471 %")')
|
|
WRITE(stdout,'(5x,"% use with input_dft = ''vdW-DF3-opt1'' or ''vdW-DF3-opt2'' %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"% * Unscreened and range-separated hybrid vdW-DF-cx functionals %")')
|
|
WRITE(stdout,'(5x,"% DOI: 10.1063/1.4986522 and 10.1088/1361-648X/ac2ad2 %")')
|
|
WRITE(stdout,'(5x,"% use with input_dft = ''vdW-DF-cx0'' and ''vdW-DF-ahcx'' %")')
|
|
WRITE(stdout,'(5x,"% * Unscreened and range-separated hybrid vdW-DF2-b86r functionals %")')
|
|
WRITE(stdout,'(5x,"% DOI: 10.1063/1.4986522 and DOI: 10.1103/PhysRevX.12.041003 %")')
|
|
WRITE(stdout,'(5x,"% use with input_dft = ''vdW-DF2-br0'' and ''vdW-DF2-ahbr'' %")')
|
|
WRITE(stdout,'(5x,"% %")')
|
|
WRITE(stdout,'(5x,"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%",//)')
|
|
|
|
IF ( iverbosity > 0 ) THEN
|
|
WRITE(stdout,'(5x,"Carrying out vdW-DF run using the following parameters:")')
|
|
WRITE(stdout,'(5X,A,I3,A,I5,A,F8.3)' ) "Nqs = ", Nqs, " Npoints = ", Nr_points, &
|
|
" r_max = ", r_max
|
|
WRITE(stdout,'(5X,"q_mesh =",4F12.8)') (q_mesh(idx), idx=1, 4)
|
|
WRITE(stdout,'(13X,4F12.8)') (q_mesh(idx), idx=5, Nqs)
|
|
END IF
|
|
|
|
END SUBROUTINE
|
|
|
|
END MODULE vdW_DF
|