! 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
