Line data Source code
1 : !--------------------------------------------------------------------------------------------------!
2 : ! CP2K: A general program to perform molecular dynamics simulations !
3 : ! Copyright 2000-2026 CP2K developers group <https://cp2k.org> !
4 : ! !
5 : ! SPDX-License-Identifier: GPL-2.0-or-later !
6 : !--------------------------------------------------------------------------------------------------!
7 :
8 : ! **************************************************************************************************
9 : !> \brief Calculation of non local dispersion functionals
10 : !> Some routines adapted from:
11 : !> Copyright (C) 2001-2009 Quantum ESPRESSO group
12 : !> Copyright (C) 2009 Brian Kolb, Timo Thonhauser - Wake Forest University
13 : !> This file is distributed under the terms of the
14 : !> GNU General Public License. See the file `License'
15 : !> in the root directory of the present distribution,
16 : !> or http://www.gnu.org/copyleft/gpl.txt .
17 : !> \author JGH
18 : ! **************************************************************************************************
19 : MODULE qs_dispersion_nonloc
20 : USE bibliography, ONLY: Dion2004,&
21 : Romanperez2009,&
22 : Sabatini2013,&
23 : cite_reference
24 : USE cp_control_types, ONLY: dft_control_type
25 : USE cp_files, ONLY: close_file,&
26 : open_file
27 : USE input_constants, ONLY: vdw_nl_DRSLL,&
28 : vdw_nl_LMKLL,&
29 : vdw_nl_RVV10,&
30 : xc_vdw_fun_nonloc
31 : USE input_section_types, ONLY: section_get_ival,&
32 : section_get_rval,&
33 : section_vals_type,&
34 : section_vals_val_get
35 : USE kinds, ONLY: default_path_length,&
36 : dp
37 : USE mathconstants, ONLY: pi,&
38 : rootpi
39 : USE message_passing, ONLY: mp_para_env_type
40 : USE pw_env_types, ONLY: pw_env_get,&
41 : pw_env_type
42 : USE pw_grid_types, ONLY: HALFSPACE,&
43 : pw_grid_type
44 : USE pw_methods, ONLY: pw_axpy,&
45 : pw_derive,&
46 : pw_scale,&
47 : pw_transfer,&
48 : pw_zero
49 : USE pw_pool_types, ONLY: pw_pool_type
50 : USE pw_types, ONLY: pw_c1d_gs_type,&
51 : pw_r3d_rs_type
52 : USE qs_dispersion_types, ONLY: qs_dispersion_type
53 : USE qs_environment_types, ONLY: get_qs_env,&
54 : qs_environment_type
55 : USE virial_types, ONLY: virial_type
56 : #include "./base/base_uses.f90"
57 :
58 : IMPLICIT NONE
59 :
60 : PRIVATE
61 :
62 : REAL(KIND=dp), PARAMETER :: epsr = 1.e-12_dp
63 : INTEGER, DIMENSION(3, 3), PARAMETER :: nd = RESHAPE([1, 0, 0, 0, 1, 0, 0, 0, 1], [3, 3])
64 :
65 : CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_dispersion_nonloc'
66 :
67 : PUBLIC :: qs_dispersion_nonloc_init, calculate_vxc_nlvdw
68 : PUBLIC :: qs_fxc_nlvdw_fdiff, qs_fxc_nlvdw_apply
69 : PUBLIC :: prepare_nlvdw_density, return_nlvdw_potential
70 :
71 : ! **************************************************************************************************
72 :
73 : CONTAINS
74 :
75 : ! **************************************************************************************************
76 : !> \brief ...
77 : !> \param dispersion_env ...
78 : !> \param para_env ...
79 : ! **************************************************************************************************
80 66 : SUBROUTINE qs_dispersion_nonloc_init(dispersion_env, para_env)
81 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
82 : TYPE(mp_para_env_type), POINTER :: para_env
83 :
84 : CHARACTER(len=*), PARAMETER :: routineN = 'qs_dispersion_nonloc_init'
85 :
86 : CHARACTER(LEN=default_path_length) :: filename
87 : INTEGER :: funit, handle, ipair, itable, nqs, &
88 : nr_points, vdw_type
89 :
90 66 : CALL timeset(routineN, handle)
91 :
92 66 : SELECT CASE (dispersion_env%nl_type)
93 : CASE DEFAULT
94 0 : CPABORT("Unknown vdW-DF functional")
95 : CASE (vdw_nl_DRSLL, vdw_nl_LMKLL)
96 34 : CALL cite_reference(Dion2004)
97 : CASE (vdw_nl_RVV10)
98 66 : CALL cite_reference(Sabatini2013)
99 : END SELECT
100 66 : CALL cite_reference(RomanPerez2009)
101 :
102 66 : vdw_type = dispersion_env%type
103 66 : SELECT CASE (vdw_type)
104 : CASE DEFAULT
105 : ! do nothing
106 : CASE (xc_vdw_fun_nonloc)
107 : ! setup information on non local functionals
108 66 : filename = dispersion_env%kernel_file_name
109 66 : IF (para_env%is_source()) THEN
110 : ! Read the kernel information from file "filename"
111 33 : CALL open_file(file_name=filename, unit_number=funit, file_form="FORMATTED")
112 33 : READ (funit, *) nqs, nr_points
113 33 : READ (funit, *) dispersion_env%r_max
114 : END IF
115 66 : CALL para_env%bcast(nqs)
116 66 : CALL para_env%bcast(nr_points)
117 66 : CALL para_env%bcast(dispersion_env%r_max)
118 462 : ALLOCATE (dispersion_env%q_mesh(nqs), dispersion_env%kernel_table(nqs*(nqs + 1)/2, 0:nr_points, 2))
119 66 : dispersion_env%nqs = nqs
120 66 : dispersion_env%nr_points = nr_points
121 66 : IF (para_env%is_source()) THEN
122 : !! Read in the values of the q points used to generate this kernel
123 693 : READ (funit, "(1p, 4e23.14)") dispersion_env%q_mesh
124 : ! The file stores kernel values followed by second derivatives, both in
125 : ! lower-triangular pair order. Keep this immutable table in packed form.
126 99 : DO itable = 1, 2
127 13959 : DO ipair = 1, nqs*(nqs + 1)/2
128 14220426 : READ (funit, "(1p, 4e23.14)") dispersion_env%kernel_table(ipair, 0:nr_points, itable)
129 : END DO
130 : END DO
131 33 : CALL close_file(unit_number=funit)
132 : END IF
133 2706 : CALL para_env%bcast(dispersion_env%q_mesh)
134 57096930 : CALL para_env%bcast(dispersion_env%kernel_table)
135 : ! 2nd derivates for interpolation
136 264 : ALLOCATE (dispersion_env%d2y_dx2(nqs, nqs))
137 66 : CALL initialize_spline_interpolation(dispersion_env%q_mesh, dispersion_env%d2y_dx2)
138 : !
139 66 : dispersion_env%q_cut = dispersion_env%q_mesh(nqs)
140 66 : dispersion_env%q_min = dispersion_env%q_mesh(1)
141 132 : dispersion_env%dk = 2.0_dp*pi/dispersion_env%r_max
142 :
143 : END SELECT
144 :
145 66 : CALL timestop(handle)
146 :
147 66 : END SUBROUTINE qs_dispersion_nonloc_init
148 :
149 : ! **************************************************************************************************
150 : !> \brief Calculates the non-local vdW functional using the method of Soler
151 : !> For spin polarized cases we use E(a,b) = E(a+b), i.e. total density
152 : !> \param vxc_r ...
153 : !> \param rho ...
154 : !> \param drho ...
155 : !> \param edispersion ...
156 : !> \param dispersion_env ...
157 : !> \param energy_only ...
158 : !> \param pw_pool ...
159 : !> \param para_env ...
160 : !> \param virial ...
161 : ! **************************************************************************************************
162 1214 : SUBROUTINE calculate_vxc_nlvdw(vxc_r, rho, drho, edispersion, &
163 : dispersion_env, energy_only, pw_pool, para_env, virial)
164 : TYPE(pw_r3d_rs_type) :: vxc_r
165 : REAL(KIND=dp), DIMENSION(:) :: rho
166 : REAL(KIND=dp), DIMENSION(:, :) :: drho
167 : REAL(KIND=dp), INTENT(OUT) :: edispersion
168 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
169 : LOGICAL, INTENT(IN) :: energy_only
170 : TYPE(pw_pool_type), POINTER :: pw_pool
171 : TYPE(mp_para_env_type), POINTER :: para_env
172 : TYPE(virial_type), OPTIONAL, POINTER :: virial
173 :
174 : CHARACTER(LEN=*), PARAMETER :: routineN = 'calculate_vxc_nlvdw'
175 :
176 : INTEGER :: handle, handle_fft, i, i_grid, idir, &
177 : nl_type, np, s
178 1214 : INTEGER, ALLOCATABLE, DIMENSION(:) :: q_low
179 : LOGICAL :: use_virial
180 : REAL(KIND=dp) :: b_value, beta, Ec_nl, sumnp
181 1214 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: dq0_dgradrho, dq0_drho, hpot, q0, &
182 1214 : theta_scale
183 1214 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: spline_coeff, u_contract
184 1214 : REAL(KIND=dp), CONTIGUOUS, POINTER :: tmp_1d(:), vxc_1d(:)
185 : TYPE(pw_c1d_gs_type) :: tmp_g
186 1214 : TYPE(pw_c1d_gs_type), ALLOCATABLE, DIMENSION(:) :: thetas_g
187 : TYPE(pw_grid_type), POINTER :: grid
188 : TYPE(pw_r3d_rs_type) :: tmp_r
189 :
190 1214 : CALL timeset(routineN, handle)
191 :
192 1214 : IF (PRESENT(virial)) THEN
193 848 : use_virial = virial%pv_calculate .AND. (.NOT. virial%pv_numer)
194 : ELSE
195 : use_virial = .FALSE.
196 : END IF
197 : IF (use_virial) THEN
198 160 : CPASSERT(.NOT. energy_only)
199 : END IF
200 :
201 1214 : nl_type = dispersion_env%nl_type
202 :
203 1214 : b_value = dispersion_env%b_value
204 1214 : beta = 0.03125_dp*(3.0_dp/(b_value**2.0_dp))**0.75_dp
205 :
206 1214 : CALL pw_pool%create_pw(tmp_g)
207 1214 : CALL pw_pool%create_pw(tmp_r)
208 1214 : np = pw_pool%pw_grid%ngpts_local
209 1214 : tmp_1d(1:np) => tmp_r%array
210 :
211 : !! ---------------------------------------------------------------------------------
212 : !! Find the value of q0 for all assigned grid points. q is defined in equations
213 : !! 11 and 12 of DION and q0 is the saturated version of q defined in equation
214 : !! 5 of SOLER. This routine also returns the derivatives of the q0s with respect
215 : !! to the charge-density and the gradient of the charge-density. These are needed
216 : !! for the potential calculated below.
217 : !! ---------------------------------------------------------------------------------
218 :
219 1214 : IF (energy_only) THEN
220 18 : ALLOCATE (q0(np))
221 0 : SELECT CASE (nl_type)
222 : CASE DEFAULT
223 0 : CPABORT("Unknown vdW-DF functional")
224 : CASE (vdw_nl_DRSLL, vdw_nl_LMKLL)
225 0 : CALL get_q0_on_grid_eo_vdw(rho, drho, q0, dispersion_env)
226 : CASE (vdw_nl_RVV10)
227 6 : CALL get_q0_on_grid_eo_rvv10(rho, drho, q0, dispersion_env)
228 : END SELECT
229 : ELSE
230 6040 : ALLOCATE (q0(np), dq0_drho(np), dq0_dgradrho(np))
231 0 : SELECT CASE (nl_type)
232 : CASE DEFAULT
233 0 : CPABORT("Unknown vdW-DF functional")
234 : CASE (vdw_nl_DRSLL, vdw_nl_LMKLL)
235 254 : CALL get_q0_on_grid_vdw(rho, drho, q0, dq0_drho, dq0_dgradrho, dispersion_env)
236 : CASE (vdw_nl_RVV10)
237 1208 : CALL get_q0_on_grid_rvv10(rho, drho, q0, dq0_drho, dq0_dgradrho, dispersion_env)
238 : END SELECT
239 : END IF
240 :
241 : ! Generate one theta channel directly in the FFT workspace. Only reciprocal-space
242 : ! channels must coexist for the convolution; no real-space np-by-nqs array is needed.
243 8498 : ALLOCATE (q_low(np), spline_coeff(np, 4), theta_scale(np))
244 1214 : CALL prepare_splines(q0, rho, dispersion_env, q_low, spline_coeff, theta_scale)
245 27922 : ALLOCATE (thetas_g(dispersion_env%nqs))
246 1214 : CALL timeset("vdW_theta_forward", handle_fft)
247 25494 : DO i = 1, dispersion_env%nqs
248 24280 : CALL build_theta(i, q_low, spline_coeff, theta_scale, dispersion_env, tmp_1d)
249 24280 : CALL pw_pool%create_pw(thetas_g(i))
250 25494 : CALL pw_transfer(tmp_r, thetas_g(i))
251 : END DO
252 1214 : CALL timestop(handle_fft)
253 1214 : DEALLOCATE (spline_coeff, theta_scale)
254 1214 : grid => thetas_g(1)%pw_grid
255 : !! ---------------------------------------------------------------------------------------------
256 : !! Carry out the integration in equation 8 of SOLER. This also turns the thetas array into the
257 : !! precursor to the u_i(k) array which is inverse fourier transformed to get the u_i(r) functions
258 : !! of SOLER equation 11. Add the energy we find to the output variable etxc.
259 : !! --------------------------------------------------------------------------------------------------
260 1214 : sumnp = np
261 1214 : CALL para_env%sum(sumnp)
262 1214 : IF (use_virial) THEN
263 : ! calculates kernel contribution to stress
264 160 : CALL vdW_energy(thetas_g, dispersion_env, Ec_nl, energy_only, virial)
265 : SELECT CASE (nl_type)
266 : CASE (vdw_nl_RVV10)
267 1422439 : Ec_nl = 0.5_dp*Ec_nl + beta*SUM(rho(:))*grid%vol/sumnp
268 : END SELECT
269 : ! calculates energy contribution to stress
270 : ! potential contribution to stress is calculated together with other potentials (Hxc)
271 640 : DO idir = 1, 3
272 640 : virial%pv_xc(idir, idir) = virial%pv_xc(idir, idir) + Ec_nl
273 : END DO
274 : ELSE
275 1054 : CALL vdW_energy(thetas_g, dispersion_env, Ec_nl, energy_only)
276 : SELECT CASE (nl_type)
277 : CASE (vdw_nl_RVV10)
278 17211814 : Ec_nl = 0.5_dp*Ec_nl + beta*SUM(rho(:))*grid%vol/sumnp
279 : END SELECT
280 : END IF
281 1214 : CALL para_env%sum(Ec_nl)
282 1214 : IF (nl_type == vdw_nl_RVV10) Ec_nl = Ec_nl*dispersion_env%scale_rvv10
283 1214 : edispersion = Ec_nl
284 :
285 1214 : IF (energy_only) THEN
286 6 : DEALLOCATE (q0, q_low)
287 : ELSE
288 : ! Accumulate the two spline contractions and their endpoint values as each
289 : ! inverse FFT finishes. Keep the original potential-side knot convention.
290 3624 : ALLOCATE (u_contract(np, 4), hpot(np))
291 1208 : !$OMP PARALLEL DO DEFAULT(NONE) SHARED(np, q_low, q0, dispersion_env, u_contract) PRIVATE(s)
292 : DO i_grid = 1, np
293 : s = q_low(i_grid)
294 : IF (s > 0 .AND. s < dispersion_env%nqs - 1) THEN
295 : IF (q0(i_grid) == dispersion_env%q_mesh(s + 1)) q_low(i_grid) = s + 1
296 : END IF
297 : u_contract(i_grid, :) = 0.0_dp
298 : END DO
299 : !$OMP END PARALLEL DO
300 1208 : CALL timeset("vdW_theta_inverse", handle_fft)
301 25368 : DO i = 1, dispersion_env%nqs
302 24160 : CALL pw_transfer(thetas_g(i), tmp_r)
303 25368 : CALL accumulate_potential(i, q_low, dispersion_env, tmp_1d, u_contract)
304 : END DO
305 1208 : CALL timestop(handle_fft)
306 :
307 : ! Write the local potential directly into its PW object.
308 1208 : vxc_1d(1:np) => vxc_r%array
309 1208 : IF (use_virial) THEN
310 160 : grid => tmp_g%pw_grid
311 : CALL get_potential(q0, dq0_drho, dq0_dgradrho, rho, q_low, u_contract, vxc_1d, hpot, &
312 160 : dispersion_env, drho, grid%dvol, virial)
313 : ELSE
314 : CALL get_potential(q0, dq0_drho, dq0_dgradrho, rho, q_low, u_contract, vxc_1d, hpot, &
315 1048 : dispersion_env)
316 : END IF
317 1208 : DEALLOCATE (u_contract, q_low, q0, dq0_drho, dq0_dgradrho)
318 954 : SELECT CASE (nl_type)
319 : CASE (vdw_nl_RVV10)
320 1208 : !$OMP PARALLEL DO DEFAULT(NONE) SHARED(np, vxc_1d, hpot, beta, dispersion_env)
321 : DO i_grid = 1, np
322 : vxc_1d(i_grid) = (0.5_dp*vxc_1d(i_grid) + beta)*dispersion_env%scale_rvv10
323 : hpot(i_grid) = 0.5_dp*dispersion_env%scale_rvv10*hpot(i_grid)
324 : END DO
325 : !$OMP END PARALLEL DO
326 : END SELECT
327 1208 : NULLIFY (vxc_1d)
328 :
329 2416 : CALL combine_nlvdw_potential(vxc_r, drho, hpot, tmp_r, tmp_g, pw_pool)
330 :
331 : END IF
332 :
333 : NULLIFY (tmp_1d)
334 :
335 25494 : DO i = 1, dispersion_env%nqs
336 25494 : CALL pw_pool%give_back_pw(thetas_g(i))
337 : END DO
338 1214 : CALL pw_pool%give_back_pw(tmp_r)
339 1214 : CALL pw_pool%give_back_pw(tmp_g)
340 :
341 1214 : DEALLOCATE (thetas_g)
342 :
343 1214 : CALL timestop(handle)
344 :
345 3642 : END SUBROUTINE calculate_vxc_nlvdw
346 :
347 : ! **************************************************************************************************
348 : !> \brief Second derivative of nl-vdW potential
349 : !> \param qs_env ...
350 : !> \param xc_section ...
351 : !> \param dispersion_env ...
352 : !> \param rho0_r ...
353 : !> \param rho1_r ...
354 : !> \param fxc_rho ...
355 : ! **************************************************************************************************
356 23448 : SUBROUTINE qs_fxc_nlvdw_apply(qs_env, xc_section, dispersion_env, rho0_r, rho1_r, fxc_rho)
357 :
358 : TYPE(qs_environment_type), POINTER :: qs_env
359 : TYPE(section_vals_type), POINTER :: xc_section
360 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
361 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: rho0_r, rho1_r, fxc_rho
362 :
363 : CHARACTER(len=*), PARAMETER :: routineN = 'qs_fxc_nlvdw_apply'
364 :
365 : INTEGER :: handle, ispin, nspins, nsteps, order, vdw
366 : LOGICAL :: vdW_nl
367 : REAL(KIND=dp) :: eps_delta
368 : TYPE(pw_env_type), POINTER :: pw_env
369 : TYPE(pw_pool_type), POINTER :: auxbas_pw_pool
370 11724 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: fxc_vdw
371 :
372 11724 : CALL timeset(routineN, handle)
373 :
374 : ! nonlocal vdW functionals
375 11724 : vdW_nl = .FALSE.
376 11724 : CALL section_vals_val_get(xc_section, "VDW_POTENTIAL%POTENTIAL_TYPE", i_val=vdw)
377 11724 : vdW_nl = (vdw == xc_vdw_fun_nonloc)
378 11724 : IF (vdW_nl) THEN
379 60 : nsteps = section_get_ival(xc_section, "NSTEPS")
380 60 : order = 2*nsteps
381 60 : eps_delta = section_get_rval(xc_section, "STEP_SIZE")
382 60 : nspins = SIZE(rho0_r)
383 60 : CALL get_qs_env(qs_env, pw_env=pw_env)
384 60 : CALL pw_env_get(pw_env, auxbas_pw_pool=auxbas_pw_pool)
385 240 : ALLOCATE (fxc_vdw(nspins))
386 120 : DO ispin = 1, nspins
387 120 : CALL auxbas_pw_pool%create_pw(fxc_vdw(ispin))
388 : END DO
389 : CALL qs_fxc_nlvdw_fdiff(qs_env, dispersion_env, rho0_r, rho1_r, &
390 60 : order, eps_delta, fxc_vdw)
391 120 : DO ispin = 1, nspins
392 60 : CALL pw_axpy(fxc_vdw(ispin), fxc_rho(ispin), 1.0_dp)
393 120 : CALL auxbas_pw_pool%give_back_pw(fxc_vdw(ispin))
394 : END DO
395 60 : DEALLOCATE (fxc_vdw)
396 :
397 : END IF
398 :
399 11724 : CALL timestop(handle)
400 :
401 11724 : END SUBROUTINE qs_fxc_nlvdw_apply
402 :
403 : ! **************************************************************************************************
404 : !> \brief Second derivative of nl-vdW potential using finite differences
405 : !> \param qs_env ...
406 : !> \param dispersion_env ...
407 : !> \param rho0_r ...
408 : !> \param rho1_r ...
409 : !> \param accuracy ...
410 : !> \param epsrho ...
411 : !> \param fxc_rho ...
412 : !>
413 : !>
414 : !> https://en.wikipedia.org/wiki/Finite_difference_coefficient
415 : !> ---------------------------------------------------------------------------------------------------
416 : !> Derivative Accuracy 4 3 2 1 0 1 2 3 4
417 : !> ---------------------------------------------------------------------------------------------------
418 : !> 1 2 -1/2 0 1/2
419 : !> 4 1/12 -2/3 0 2/3 -1/12
420 : !> 6 -1/60 3/20 -3/4 0 3/4 -3/20 1/60
421 : !> 8 1/280 -4/105 1/5 -4/5 0 4/5 -1/5 4/105 -1/280
422 : !> ---------------------------------------------------------------------------------------------------
423 : !> 2 2 1 -2 1
424 : !> 4 -1/12 4/3 -5/2 4/3 -1/12
425 : !> 6 1/90 -3/20 3/2 -49/18 3/2 -3/20 1/90
426 : !> 8 -1/560 8/315 -1/5 8/5 -205/72 8/5 -1/5 8/315 -1/560
427 : !> ---------------------------------------------------------------------------------------------------
428 : ! **************************************************************************************************
429 60 : SUBROUTINE qs_fxc_nlvdw_fdiff(qs_env, dispersion_env, rho0_r, rho1_r, accuracy, epsrho, fxc_rho)
430 :
431 : TYPE(qs_environment_type), POINTER :: qs_env
432 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
433 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: rho0_r, rho1_r
434 : INTEGER, INTENT(IN) :: accuracy
435 : REAL(KIND=dp), INTENT(IN) :: epsrho
436 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: fxc_rho
437 :
438 : CHARACTER(len=*), PARAMETER :: routineN = 'qs_fxc_nlvdw_fdiff'
439 :
440 : INTEGER :: handle, ispin, istep, np, nspins, nstep
441 : REAL(KIND=dp) :: beta, edisp, oeps1
442 60 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: rho, rho1, rhonl
443 60 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: drho, drho1, drhonl
444 : REAL(KIND=dp), DIMENSION(-4:4) :: ak
445 : TYPE(dft_control_type), POINTER :: dft_control
446 : TYPE(mp_para_env_type), POINTER :: para_env
447 : TYPE(pw_env_type), POINTER :: pw_env
448 : TYPE(pw_pool_type), POINTER :: pw_pool, vdw_pw_pool
449 : TYPE(pw_r3d_rs_type) :: vxcnl
450 60 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: vxc00
451 :
452 60 : CALL timeset(routineN, handle)
453 :
454 60 : ak = 0.0_dp
455 60 : SELECT CASE (accuracy)
456 : CASE (:4)
457 0 : nstep = 2
458 0 : ak(-2:2) = [1.0_dp, -8.0_dp, 0.0_dp, 8.0_dp, -1.0_dp]/12.0_dp
459 : CASE (5:7)
460 480 : nstep = 3
461 480 : ak(-3:3) = [-1.0_dp, 9.0_dp, -45.0_dp, 0.0_dp, 45.0_dp, -9.0_dp, 1.0_dp]/60.0_dp
462 : CASE (8:)
463 0 : nstep = 4
464 : ak(-4:4) = [1.0_dp, -32.0_dp/3.0_dp, 56.0_dp, -224.0_dp, 0.0_dp, &
465 60 : 224.0_dp, -56.0_dp, 32.0_dp/3.0_dp, -1.0_dp]/280.0_dp
466 : END SELECT
467 :
468 : CALL get_qs_env(qs_env, dft_control=dft_control, &
469 60 : pw_env=pw_env, para_env=para_env)
470 60 : CALL pw_env_get(pw_env, auxbas_pw_pool=pw_pool)
471 60 : CALL pw_env_get(pw_env, vdw_pw_pool=vdw_pw_pool)
472 :
473 60 : nspins = dft_control%nspins
474 :
475 240 : ALLOCATE (vxc00(nspins))
476 120 : DO ispin = 1, nspins
477 60 : CALL pw_pool%create_pw(vxc00(ispin))
478 120 : CALL pw_zero(fxc_rho(ispin))
479 : END DO
480 :
481 60 : np = vdw_pw_pool%pw_grid%ngpts_local
482 300 : ALLOCATE (rho(np), drho(np, 3))
483 60 : CALL prepare_nlvdw_density(rho0_r, rho, drho, vdw_pw_pool, pw_pool)
484 180 : ALLOCATE (rho1(np), drho1(np, 3))
485 60 : CALL prepare_nlvdw_density(rho1_r, rho1, drho1, vdw_pw_pool, pw_pool)
486 180 : ALLOCATE (rhonl(np), drhonl(np, 3))
487 :
488 480 : DO istep = -nstep, nstep
489 :
490 480 : IF (ak(istep) /= 0.0_dp) THEN
491 360 : beta = REAL(istep, KIND=dp)*epsrho
492 7148520 : rhonl(:) = rho(:) + beta*rho1(:)
493 21445920 : drhonl(:, :) = drho(:, :) + beta*drho1(:, :)
494 360 : CALL vdw_pw_pool%create_pw(vxcnl)
495 : CALL calculate_vxc_nlvdw(vxcnl, rhonl, drhonl, edisp, dispersion_env, &
496 360 : .FALSE., vdw_pw_pool, para_env)
497 720 : DO ispin = 1, nspins
498 720 : CALL pw_zero(vxc00(ispin))
499 : END DO
500 360 : CALL return_nlvdw_potential(vxc00, vxcnl, vdw_pw_pool, pw_pool)
501 360 : CALL vdw_pw_pool%give_back_pw(vxcnl)
502 720 : DO ispin = 1, nspins
503 720 : CALL pw_axpy(vxc00(ispin), fxc_rho(ispin), ak(istep))
504 : END DO
505 : END IF
506 :
507 : END DO
508 :
509 60 : DEALLOCATE (rho, drho)
510 60 : DEALLOCATE (rho1, drho1)
511 60 : DEALLOCATE (rhonl, drhonl)
512 120 : DO ispin = 1, nspins
513 120 : CALL pw_pool%give_back_pw(vxc00(ispin))
514 : END DO
515 60 : DEALLOCATE (vxc00)
516 :
517 60 : oeps1 = 1.0_dp/epsrho
518 120 : DO ispin = 1, nspins
519 120 : CALL pw_scale(fxc_rho(ispin), oeps1)
520 : END DO
521 :
522 60 : CALL timestop(handle)
523 :
524 60 : END SUBROUTINE qs_fxc_nlvdw_fdiff
525 :
526 : ! **************************************************************************************************
527 : !> \brief Transform the input density to a 1d grid used by the nl-vdW routines
528 : !> \param rho_r ...
529 : !> \param rho ...
530 : !> \param drho ...
531 : !> \param pw_pool ...
532 : !> \param xc_pw_pool ...
533 : ! **************************************************************************************************
534 974 : SUBROUTINE prepare_nlvdw_density(rho_r, rho, drho, pw_pool, xc_pw_pool)
535 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: rho_r
536 : REAL(KIND=dp), DIMENSION(:) :: rho
537 : REAL(KIND=dp), DIMENSION(:, :) :: drho
538 : TYPE(pw_pool_type), POINTER :: pw_pool, xc_pw_pool
539 :
540 : CHARACTER(LEN=*), PARAMETER :: routineN = 'prepare_nlvdw_density'
541 :
542 : INTEGER :: handle, i, idir, ispin, np, nspins, p, &
543 : q, r, s
544 : INTEGER, DIMENSION(3) :: hi, lo, n
545 : TYPE(pw_c1d_gs_type) :: rho_g, rho_tot_g, tmp_g
546 : TYPE(pw_r3d_rs_type) :: tmp_r
547 :
548 974 : CALL timeset(routineN, handle)
549 :
550 974 : nspins = SIZE(rho_r)
551 :
552 974 : CALL pw_pool%create_pw(tmp_g)
553 974 : CALL pw_pool%create_pw(tmp_r)
554 :
555 : ! Sum the spin densities on the vdW grid
556 : ! before transforming or differentiating.
557 974 : CALL xc_pw_pool%create_pw(rho_g)
558 974 : CALL pw_pool%create_pw(rho_tot_g)
559 974 : CALL pw_zero(rho_tot_g)
560 1968 : DO ispin = 1, nspins
561 994 : CALL pw_transfer(rho_r(ispin), rho_g)
562 994 : CALL pw_transfer(rho_g, tmp_g)
563 1968 : CALL pw_axpy(tmp_g, rho_tot_g, 1._dp)
564 : END DO
565 974 : CALL pw_transfer(rho_tot_g, tmp_r)
566 974 : CALL xc_pw_pool%give_back_pw(rho_g)
567 :
568 3896 : np = SIZE(tmp_r%array)
569 974 : CPASSERT(np == SIZE(rho))
570 974 : CPASSERT(np == SIZE(drho, 1))
571 3896 : DO i = 1, 3
572 2922 : lo(i) = LBOUND(tmp_r%array, i)
573 2922 : hi(i) = UBOUND(tmp_r%array, i)
574 3896 : n(i) = hi(i) - lo(i) + 1
575 : END DO
576 : !$OMP PARALLEL DO DEFAULT(NONE) &
577 974 : !$OMP SHARED(n, lo, rho, tmp_r) PRIVATE(s) COLLAPSE(3)
578 : DO r = 0, n(3) - 1
579 : DO q = 0, n(2) - 1
580 : DO p = 0, n(1) - 1
581 : s = r*n(2)*n(1) + q*n(1) + p + 1
582 : rho(s) = tmp_r%array(p + lo(1), q + lo(2), r + lo(3))
583 : END DO
584 : END DO
585 : END DO
586 : !$OMP END PARALLEL DO
587 3896 : DO idir = 1, 3
588 2922 : CALL pw_transfer(rho_tot_g, tmp_g)
589 2922 : CALL pw_derive(tmp_g, nd(:, idir))
590 2922 : CALL pw_transfer(tmp_g, tmp_r)
591 : !$OMP PARALLEL DO DEFAULT(NONE) &
592 3896 : !$OMP SHARED(idir, n, lo, drho, tmp_r) PRIVATE(s) COLLAPSE(3)
593 : DO r = 0, n(3) - 1
594 : DO q = 0, n(2) - 1
595 : DO p = 0, n(1) - 1
596 : s = r*n(2)*n(1) + q*n(1) + p + 1
597 : drho(s, idir) = tmp_r%array(p + lo(1), q + lo(2), r + lo(3))
598 : END DO
599 : END DO
600 : END DO
601 : !$OMP END PARALLEL DO
602 : END DO
603 974 : CALL pw_pool%give_back_pw(rho_tot_g)
604 974 : CALL pw_pool%give_back_pw(tmp_r)
605 974 : CALL pw_pool%give_back_pw(tmp_g)
606 :
607 974 : CALL timestop(handle)
608 :
609 974 : END SUBROUTINE prepare_nlvdw_density
610 :
611 : ! **************************************************************************************************
612 : !> \brief Finalizes the potential calculation
613 : !> \param vxc_r ...
614 : !> \param drho ...
615 : !> \param hpot ...
616 : !> \param tmp_r ...
617 : !> \param tmp_g ...
618 : !> \param pw_pool ...
619 : ! **************************************************************************************************
620 1208 : SUBROUTINE combine_nlvdw_potential(vxc_r, drho, hpot, tmp_r, tmp_g, pw_pool)
621 : TYPE(pw_r3d_rs_type) :: vxc_r
622 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: drho
623 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: hpot
624 : TYPE(pw_r3d_rs_type) :: tmp_r
625 : TYPE(pw_c1d_gs_type) :: tmp_g
626 : TYPE(pw_pool_type), POINTER :: pw_pool
627 :
628 : CHARACTER(LEN=*), PARAMETER :: routineN = 'combine_nlvdw_potential'
629 :
630 : INTEGER :: handle, i, idir, np, p, q, r, s
631 : INTEGER, DIMENSION(1:3) :: hi, lo, n
632 1208 : REAL(KIND=dp), CONTIGUOUS, POINTER :: tmp_1d(:)
633 : TYPE(pw_c1d_gs_type) :: div_g
634 :
635 1208 : CALL timeset(routineN, handle)
636 :
637 4832 : np = SIZE(tmp_r%array)
638 4832 : tmp_1d(1:np) => tmp_r%array
639 4832 : DO i = 1, 3
640 3624 : lo(i) = LBOUND(tmp_r%array, i)
641 3624 : hi(i) = UBOUND(tmp_r%array, i)
642 4832 : n(i) = hi(i) - lo(i) + 1
643 : END DO
644 :
645 : ! Sum the derivatives before the inverse FFT. Keep the real-space projection
646 : ! before transferring the potential to a possibly different XC grid.
647 1208 : CALL pw_pool%create_pw(div_g)
648 1208 : CALL pw_zero(div_g)
649 4832 : DO idir = 1, 3
650 : !$OMP PARALLEL DO DEFAULT(NONE) &
651 3624 : !$OMP SHARED(n, lo, tmp_r, hpot, drho, idir) PRIVATE(s) COLLAPSE(3)
652 : DO r = 0, n(3) - 1
653 : DO q = 0, n(2) - 1
654 : DO p = 0, n(1) - 1
655 : s = r*n(2)*n(1) + q*n(1) + p + 1
656 : tmp_r%array(p + lo(1), q + lo(2), r + lo(3)) = hpot(s)*drho(s, idir)
657 : END DO
658 : END DO
659 : END DO
660 : !$OMP END PARALLEL DO
661 3624 : CALL pw_transfer(tmp_r, tmp_g)
662 3624 : CALL pw_derive(tmp_g, nd(:, idir))
663 4832 : CALL pw_axpy(tmp_g, div_g, 1._dp)
664 : END DO
665 :
666 1208 : CALL pw_transfer(div_g, tmp_r)
667 1208 : CALL pw_axpy(tmp_r, vxc_r, -1._dp)
668 :
669 1208 : CALL pw_pool%give_back_pw(div_g)
670 :
671 1208 : CALL timestop(handle)
672 :
673 1208 : END SUBROUTINE combine_nlvdw_potential
674 :
675 : ! **************************************************************************************************
676 : !> \brief Take the nl-vdW potential and return it on the XC grid and for all spin channels
677 : !> \param vxc_rho ...
678 : !> \param vxc_r ...
679 : !> \param pw_pool ...
680 : !> \param xc_pw_pool ...
681 : ! **************************************************************************************************
682 1208 : SUBROUTINE return_nlvdw_potential(vxc_rho, vxc_r, pw_pool, xc_pw_pool)
683 : TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER :: vxc_rho
684 : TYPE(pw_r3d_rs_type) :: vxc_r
685 : TYPE(pw_pool_type), POINTER :: pw_pool, xc_pw_pool
686 :
687 : CHARACTER(LEN=*), PARAMETER :: routineN = 'return_nlvdw_potential'
688 :
689 : INTEGER :: handle, ispin, nspins
690 : TYPE(pw_c1d_gs_type) :: tmp_g, vxc_g
691 : TYPE(pw_r3d_rs_type) :: tmp_r
692 :
693 1208 : CALL timeset(routineN, handle)
694 :
695 1208 : nspins = SIZE(vxc_rho)
696 :
697 1208 : CALL pw_pool%create_pw(tmp_g)
698 1208 : CALL pw_transfer(vxc_r, tmp_g)
699 1208 : CALL xc_pw_pool%create_pw(vxc_g)
700 1208 : CALL pw_transfer(tmp_g, vxc_g)
701 1208 : CALL xc_pw_pool%create_pw(tmp_r)
702 1208 : CALL pw_transfer(vxc_g, tmp_r)
703 2436 : DO ispin = 1, nspins
704 2436 : CALL pw_axpy(tmp_r, vxc_rho(ispin), 1._dp)
705 : END DO
706 1208 : CALL pw_pool%give_back_pw(tmp_g)
707 1208 : CALL xc_pw_pool%give_back_pw(vxc_g)
708 1208 : CALL xc_pw_pool%give_back_pw(tmp_r)
709 :
710 1208 : CALL timestop(handle)
711 :
712 1208 : END SUBROUTINE return_nlvdw_potential
713 :
714 : ! **************************************************************************************************
715 : !> \brief This routine carries out the integration of equation 8 of SOLER. It returns the non-local
716 : !> exchange-correlation energy and the u_alpha(k) arrays used to find the u_alpha(r) arrays via
717 : !> equations 11 and 12 in SOLER.
718 : !> energy contribution to stress is added in qs_force
719 : !> \param thetas_g ...
720 : !> \param dispersion_env ...
721 : !> \param vdW_xc_energy ...
722 : !> \param energy_only ...
723 : !> \param virial ...
724 : !> \par History
725 : !> OpenMP added: Aug 2016 MTucker
726 : ! **************************************************************************************************
727 1214 : SUBROUTINE vdW_energy(thetas_g, dispersion_env, vdW_xc_energy, energy_only, virial)
728 : TYPE(pw_c1d_gs_type), DIMENSION(:), INTENT(IN) :: thetas_g
729 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
730 : REAL(KIND=dp), INTENT(OUT) :: vdW_xc_energy
731 : LOGICAL, INTENT(IN) :: energy_only
732 : TYPE(virial_type), OPTIONAL, POINTER :: virial
733 :
734 : CHARACTER(LEN=*), PARAMETER :: routineN = 'vdW_energy'
735 : REAL(KIND=dp), PARAMETER :: eps_g2 = 1.0E-10_dp
736 :
737 : INTEGER :: handle, ig, iq, l, m, nl_type, nqs, &
738 : q1_i, q2_i
739 : LOGICAL :: use_virial
740 : REAL(KIND=dp) :: g, g2, g2_last, g_multiplier, gm
741 1214 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: theta_im, theta_re, u_im, u_re
742 1214 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: dkernel_of_dk, kernel_of_k
743 : REAL(KIND=dp), DIMENSION(3, 3) :: virial_thread
744 : TYPE(pw_grid_type), POINTER :: grid
745 :
746 1214 : CALL timeset(routineN, handle)
747 1214 : nqs = dispersion_env%nqs
748 :
749 1214 : use_virial = PRESENT(virial)
750 1214 : virial_thread(:, :) = 0.0_dp ! always initialize to avoid floating point exceptions in OMP REDUCTION
751 :
752 1214 : vdW_xc_energy = 0._dp
753 1214 : grid => thetas_g(1)%pw_grid
754 :
755 1214 : IF (grid%grid_span == HALFSPACE) THEN
756 : g_multiplier = 2._dp
757 : ELSE
758 1214 : g_multiplier = 1._dp
759 : END IF
760 :
761 1214 : nl_type = dispersion_env%nl_type
762 :
763 : !$OMP PARALLEL DEFAULT(NONE) &
764 : !$OMP SHARED(nqs, energy_only, grid, dispersion_env, use_virial, thetas_g, &
765 : !$OMP g_multiplier, nl_type) &
766 : !$OMP PRIVATE(g2_last, kernel_of_k, dkernel_of_dk, theta_re, theta_im, &
767 : !$OMP g2, g, iq, q2_i, u_re, u_im, q1_i, gm, l, m) &
768 1214 : !$OMP REDUCTION(+:vdW_xc_energy, virial_thread)
769 :
770 : g2_last = HUGE(0._dp)
771 :
772 : ALLOCATE (kernel_of_k(nqs, nqs))
773 : IF (use_virial) ALLOCATE (dkernel_of_dk(nqs, nqs))
774 : ALLOCATE (theta_re(nqs), theta_im(nqs), u_re(nqs), u_im(nqs))
775 :
776 : !$OMP DO
777 : DO ig = 1, grid%ngpts_cut_local
778 : g2 = grid%gsq(ig)
779 : IF (ABS(g2 - g2_last) > eps_g2) THEN
780 : g2_last = g2
781 : g = SQRT(g2)
782 : IF (use_virial) THEN
783 : CALL interpolate_kernel(g, kernel_of_k, dispersion_env, dispersion_env%kernel_table, dkernel_of_dk)
784 : ELSE
785 : CALL interpolate_kernel(g, kernel_of_k, dispersion_env, dispersion_env%kernel_table)
786 : END IF
787 : END IF
788 : ! Save all inputs before in-place output. Vectorize over output channels;
789 : ! the input-channel accumulation order remains unchanged for every output.
790 : DO iq = 1, nqs
791 : theta_re(iq) = REAL(thetas_g(iq)%array(ig), KIND=dp)
792 : theta_im(iq) = AIMAG(thetas_g(iq)%array(ig))
793 : END DO
794 : u_re(:) = 0.0_dp
795 : u_im(:) = 0.0_dp
796 : DO q1_i = 1, nqs
797 : !$OMP SIMD
798 : DO q2_i = 1, nqs
799 : u_re(q2_i) = u_re(q2_i) + kernel_of_k(q2_i, q1_i)*theta_re(q1_i)
800 : u_im(q2_i) = u_im(q2_i) + kernel_of_k(q2_i, q1_i)*theta_im(q1_i)
801 : END DO
802 : !$OMP END SIMD
803 : END DO
804 : DO q2_i = 1, nqs
805 : IF (ig < grid%first_gne0) THEN
806 : vdW_xc_energy = vdW_xc_energy + (u_re(q2_i)*theta_re(q2_i) + u_im(q2_i)*theta_im(q2_i))
807 : ELSE
808 : vdW_xc_energy = vdW_xc_energy &
809 : + g_multiplier*(u_re(q2_i)*theta_re(q2_i) + u_im(q2_i)*theta_im(q2_i))
810 : END IF
811 : IF (.NOT. energy_only) thetas_g(q2_i)%array(ig) = CMPLX(u_re(q2_i), u_im(q2_i), KIND=dp)
812 : END DO
813 :
814 : IF (use_virial .AND. ig >= grid%first_gne0) THEN
815 : ! Reduce over all channel pairs before assembling the stress tensor.
816 : gm = 0.0_dp
817 : DO q2_i = 1, nqs
818 : !$OMP SIMD REDUCTION(+:gm)
819 : DO q1_i = 1, nqs
820 : gm = gm + dkernel_of_dk(q1_i, q2_i) &
821 : *(theta_re(q1_i)*theta_re(q2_i) + theta_im(q1_i)*theta_im(q2_i))
822 : END DO
823 : !$OMP END SIMD
824 : END DO
825 : gm = 0.5_dp*g_multiplier*grid%vol*gm
826 : IF (nl_type == vdw_nl_RVV10) gm = 0.5_dp*gm
827 : DO l = 1, 3
828 : DO m = 1, l
829 : virial_thread(l, m) = virial_thread(l, m) - gm*(grid%g(l, ig)*grid%g(m, ig))/g
830 : END DO
831 : END DO
832 : END IF
833 : END DO
834 : !$OMP END DO
835 :
836 : DEALLOCATE (theta_re, theta_im, u_re, u_im, kernel_of_k)
837 : IF (use_virial) DEALLOCATE (dkernel_of_dk)
838 :
839 : !$OMP END PARALLEL
840 :
841 1214 : vdW_xc_energy = vdW_xc_energy*grid%vol*0.5_dp
842 :
843 1214 : IF (use_virial) THEN
844 640 : DO l = 1, 3
845 960 : DO m = 1, (l - 1)
846 480 : virial%pv_xc(l, m) = virial%pv_xc(l, m) + virial_thread(l, m)
847 960 : virial%pv_xc(m, l) = virial%pv_xc(l, m)
848 : END DO
849 480 : m = l
850 640 : virial%pv_xc(l, m) = virial%pv_xc(l, m) + virial_thread(l, m)
851 : END DO
852 : END IF
853 :
854 1214 : CALL timestop(handle)
855 :
856 1214 : END SUBROUTINE vdW_energy
857 :
858 : ! **************************************************************************************************
859 : !> \brief This routine finds the non-local correlation contribution to the potential
860 : !> (i.e. the derivative of the non-local piece of the energy with respect to
861 : !> density) given in SOLER equation 10. The u_alpha(k) functions were found
862 : !> while calculating the energy. Their spline contractions are accumulated during the inverse FFTs.
863 : !> Most of the required derivatives were calculated in the "get_q0_on_grid"
864 : !> routine, but the derivative of the interpolation polynomials, P_alpha(q),
865 : !> (SOLER equation 3) with respect to q is interpolated here, along with the
866 : !> polynomials themselves.
867 : !> \param q0 ...
868 : !> \param dq0_drho ...
869 : !> \param dq0_dgradrho ...
870 : !> \param total_rho ...
871 : !> \param q_low_grid Lower spline endpoint at each grid point
872 : !> \param u_contract Second-derivative contractions and endpoint values
873 : !> \param potential ...
874 : !> \param h_prefactor ...
875 : !> \param dispersion_env ...
876 : !> \param drho ...
877 : !> \param dvol ...
878 : !> \param virial ...
879 : !> \par History
880 : !> OpenMP added: Aug 2016 MTucker
881 : ! **************************************************************************************************
882 1208 : SUBROUTINE get_potential(q0, dq0_drho, dq0_dgradrho, total_rho, q_low_grid, u_contract, potential, h_prefactor, &
883 1208 : dispersion_env, drho, dvol, virial)
884 :
885 : REAL(dp), DIMENSION(:), INTENT(in) :: q0, dq0_drho, dq0_dgradrho, total_rho
886 : INTEGER, DIMENSION(:), INTENT(IN) :: q_low_grid
887 : REAL(dp), DIMENSION(:, :), INTENT(in) :: u_contract
888 : REAL(dp), DIMENSION(:), INTENT(out) :: potential, h_prefactor
889 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
890 : REAL(dp), DIMENSION(:, :), INTENT(in), OPTIONAL :: drho
891 : REAL(dp), INTENT(IN), OPTIONAL :: dvol
892 : TYPE(virial_type), OPTIONAL, POINTER :: virial
893 :
894 : CHARACTER(len=*), PARAMETER :: routineN = 'get_potential'
895 :
896 : INTEGER :: handle, i_grid, l, m, nl_type, nqs, &
897 : q_hi, q_low
898 : LOGICAL :: use_virial
899 : REAL(dp) :: a, b, b_value, c, const, d, dq, dq_6, e, &
900 : f, prefactor, tmp_1_2, tmp_1_4, &
901 : tmp_3_4, u_dp_dq0, u_p
902 : REAL(dp), DIMENSION(3, 3) :: virial_thread
903 1208 : REAL(dp), DIMENSION(:), POINTER :: q_mesh
904 :
905 1208 : CALL timeset(routineN, handle)
906 :
907 1208 : use_virial = PRESENT(virial)
908 1208 : CPASSERT(.NOT. use_virial .OR. PRESENT(drho))
909 1208 : CPASSERT(.NOT. use_virial .OR. PRESENT(dvol))
910 :
911 1208 : virial_thread(:, :) = 0.0_dp ! always initialize to avoid floating point exceptions in OMP REDUCTION
912 1208 : b_value = dispersion_env%b_value
913 1208 : const = 1.0_dp/(3.0_dp*b_value**(3.0_dp/2.0_dp)*pi**(5.0_dp/4.0_dp))
914 :
915 1208 : q_mesh => dispersion_env%q_mesh
916 1208 : nqs = dispersion_env%nqs
917 1208 : nl_type = dispersion_env%nl_type
918 :
919 : !$OMP PARALLEL DEFAULT(NONE) &
920 : !$OMP SHARED(nqs, u_contract, q_low_grid, q_mesh, q0, nl_type, potential, h_prefactor, &
921 : !$OMP dq0_drho, dq0_dgradrho, total_rho, const, use_virial, drho, dvol, virial) &
922 : !$OMP PRIVATE(q_low, q_hi, dq, dq_6, A, b, c, d, e, f, u_p, u_dp_dq0, &
923 : !$OMP prefactor, l, m, tmp_1_2, tmp_1_4, tmp_3_4) &
924 1208 : !$OMP REDUCTION(+:virial_thread)
925 :
926 : !$OMP DO
927 : DO i_grid = 1, SIZE(q0)
928 : potential(i_grid) = 0.0_dp
929 : h_prefactor(i_grid) = 0.0_dp
930 : IF (nl_type == vdw_nl_RVV10 .AND. total_rho(i_grid) <= epsr) CYCLE
931 : q_low = q_low_grid(i_grid)
932 : q_hi = q_low + 1
933 :
934 : dq = q_mesh(q_hi) - q_mesh(q_low)
935 : dq_6 = dq/6.0_dp
936 :
937 : a = (q_mesh(q_hi) - q0(i_grid))/dq
938 : b = (q0(i_grid) - q_mesh(q_low))/dq
939 : c = (a**3 - a)*dq*dq_6
940 : d = (b**3 - b)*dq*dq_6
941 : e = (3.0_dp*a**2 - 1.0_dp)*dq_6
942 : f = (3.0_dp*b**2 - 1.0_dp)*dq_6
943 :
944 : u_p = a*u_contract(i_grid, 3) + b*u_contract(i_grid, 4) &
945 : + c*u_contract(i_grid, 1) + d*u_contract(i_grid, 2)
946 : u_dp_dq0 = (u_contract(i_grid, 4) - u_contract(i_grid, 3))/dq &
947 : - e*u_contract(i_grid, 1) + f*u_contract(i_grid, 2)
948 :
949 : !! The first term in equation 13 of SOLER
950 : SELECT CASE (nl_type)
951 : CASE DEFAULT
952 : CPABORT("Unknown vdW-DF functional")
953 : CASE (vdw_nl_DRSLL, vdw_nl_LMKLL)
954 : potential(i_grid) = u_p + u_dp_dq0*dq0_drho(i_grid)
955 : prefactor = u_dp_dq0*dq0_dgradrho(i_grid)
956 : CASE (vdw_nl_RVV10)
957 : tmp_1_2 = SQRT(total_rho(i_grid))
958 : tmp_1_4 = SQRT(tmp_1_2)
959 : tmp_3_4 = tmp_1_4*tmp_1_4*tmp_1_4
960 : potential(i_grid) = const*0.75_dp/tmp_1_4*u_p + const*tmp_3_4*u_dp_dq0*dq0_drho(i_grid)
961 : prefactor = const*tmp_3_4*u_dp_dq0*dq0_dgradrho(i_grid)
962 : END SELECT
963 : IF (q0(i_grid) /= q_mesh(nqs)) THEN
964 : h_prefactor(i_grid) = prefactor
965 : END IF
966 :
967 : ! The saturation guard applies only to h_prefactor, not to the virial.
968 : IF (use_virial .AND. ABS(prefactor) > 0.0_dp) THEN
969 : IF (nl_type == vdw_nl_RVV10) prefactor = 0.5_dp*prefactor
970 : prefactor = prefactor*dvol
971 : DO l = 1, 3
972 : DO m = 1, l
973 : virial_thread(l, m) = virial_thread(l, m) - prefactor*drho(i_grid, l)*drho(i_grid, m)
974 : END DO
975 : END DO
976 : END IF
977 : END DO ! i_grid = 1, SIZE(q0)
978 : !$OMP END DO
979 :
980 : !$OMP END PARALLEL
981 :
982 1208 : IF (use_virial) THEN
983 640 : DO l = 1, 3
984 960 : DO m = 1, (l - 1)
985 480 : virial%pv_xc(l, m) = virial%pv_xc(l, m) + virial_thread(l, m)
986 960 : virial%pv_xc(m, l) = virial%pv_xc(l, m)
987 : END DO
988 480 : m = l
989 640 : virial%pv_xc(l, m) = virial%pv_xc(l, m) + virial_thread(l, m)
990 : END DO
991 : END IF
992 :
993 1208 : CALL timestop(handle)
994 1208 : END SUBROUTINE get_potential
995 :
996 : ! **************************************************************************************************
997 : !> \brief calculates exponent = sum(from i=1 to hi, ((alpha)**i)/i) ) without <<< calling power >>>
998 : !> \param hi = upper index for sum
999 : !> \param alpha ...
1000 : !> \param exponent = output value
1001 : !> \par History
1002 : !> Created: MTucker, Aug 2016
1003 : ! **************************************************************************************************
1004 72326 : ELEMENTAL SUBROUTINE calculate_exponent(hi, alpha, exponent)
1005 : INTEGER, INTENT(in) :: hi
1006 : REAL(dp), INTENT(in) :: alpha
1007 : REAL(dp), INTENT(out) :: exponent
1008 :
1009 : INTEGER :: i
1010 : REAL(dp) :: multiplier
1011 :
1012 72326 : multiplier = alpha
1013 72326 : exponent = alpha
1014 :
1015 867912 : DO i = 2, hi
1016 795586 : multiplier = multiplier*alpha
1017 867912 : exponent = exponent + (multiplier/i)
1018 : END DO
1019 72326 : END SUBROUTINE calculate_exponent
1020 :
1021 : ! **************************************************************************************************
1022 : !> \brief calculate exponent = sum(from i=1 to hi, ((alpha)**i)/i) ) without calling power
1023 : !> also calculates derivative using similar series
1024 : !> \param hi = upper index for sum
1025 : !> \param alpha ...
1026 : !> \param exponent = output value
1027 : !> \param derivative ...
1028 : !> \par History
1029 : !> Created: MTucker, Aug 2016
1030 : ! **************************************************************************************************
1031 22789181 : ELEMENTAL SUBROUTINE calculate_exponent_derivative(hi, alpha, exponent, derivative)
1032 : INTEGER, INTENT(in) :: hi
1033 : REAL(dp), INTENT(in) :: alpha
1034 : REAL(dp), INTENT(out) :: exponent, derivative
1035 :
1036 : INTEGER :: i
1037 : REAL(dp) :: multiplier
1038 :
1039 22789181 : derivative = 0.0d0
1040 22789181 : multiplier = 1.0d0
1041 22789181 : exponent = 0.0d0
1042 :
1043 296259353 : DO i = 1, hi
1044 273470172 : derivative = derivative + multiplier
1045 273470172 : multiplier = multiplier*alpha
1046 296259353 : exponent = exponent + (multiplier/i)
1047 : END DO
1048 22789181 : END SUBROUTINE calculate_exponent_derivative
1049 :
1050 : !! This routine first calculates the q value defined in (DION equations 11 and 12), then
1051 : !! saturates it according to (SOLER equation 5).
1052 : ! **************************************************************************************************
1053 : !> \brief This routine first calculates the q value defined in (DION equations 11 and 12), then
1054 : !> saturates it according to (SOLER equation 5).
1055 : !> \param total_rho ...
1056 : !> \param gradient_rho ...
1057 : !> \param q0 ...
1058 : !> \param dq0_drho ...
1059 : !> \param dq0_dgradrho ...
1060 : !> \param dispersion_env ...
1061 : ! **************************************************************************************************
1062 254 : SUBROUTINE get_q0_on_grid_vdw(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho, dispersion_env)
1063 : !!
1064 : !! more specifically it calculates the following
1065 : !!
1066 : !! q0(ir) = q0 as defined above
1067 : !! dq0_drho(ir) = total_rho * d q0 /d rho
1068 : !! dq0_dgradrho = total_rho / |gradient_rho| * d q0 / d |gradient_rho|
1069 : !!
1070 : REAL(dp), INTENT(IN) :: total_rho(:), gradient_rho(:, :)
1071 : REAL(dp), INTENT(OUT) :: q0(:), dq0_drho(:), dq0_dgradrho(:)
1072 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1073 :
1074 : INTEGER, PARAMETER :: m_cut = 12
1075 : REAL(dp), PARAMETER :: LDA_A = 0.031091_dp, LDA_a1 = 0.2137_dp, LDA_b1 = 7.5957_dp, &
1076 : LDA_b2 = 3.5876_dp, LDA_b3 = 1.6382_dp, LDA_b4 = 0.49294_dp
1077 :
1078 : INTEGER :: i_grid
1079 : REAL(dp) :: dq0_dq, exponent, gradient_correction, &
1080 : kF, LDA_1, LDA_2, q, q__q_cut, q_cut, &
1081 : q_min, r_s, sqrt_r_s, Z_ab
1082 :
1083 254 : q_cut = dispersion_env%q_cut
1084 254 : q_min = dispersion_env%q_min
1085 254 : SELECT CASE (dispersion_env%nl_type)
1086 : CASE DEFAULT
1087 0 : CPABORT("Unknown vdW-DF functional")
1088 : CASE (vdw_nl_DRSLL)
1089 48 : Z_ab = -0.8491_dp
1090 : CASE (vdw_nl_LMKLL)
1091 254 : Z_ab = -1.887_dp
1092 : END SELECT
1093 :
1094 : !$OMP PARALLEL DO DEFAULT(NONE) &
1095 : !$OMP SHARED(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho, q_cut, q_min, Z_ab) &
1096 : !$OMP PRIVATE(dq0_dq, exponent, gradient_correction, kF, LDA_1, LDA_2, q, q__q_cut, r_s, sqrt_r_s) &
1097 254 : !$OMP SCHEDULE(STATIC)
1098 : DO i_grid = 1, SIZE(total_rho)
1099 : q0(i_grid) = q_cut
1100 : dq0_drho(i_grid) = 0.0_dp
1101 : dq0_dgradrho(i_grid) = 0.0_dp
1102 :
1103 : !! This prevents numerical problems. If the charge density is negative (an
1104 : !! unphysical situation), we simply treat it as very small. In that case,
1105 : !! q0 will be very large and will be saturated. For a saturated q0 the derivative
1106 : !! dq0_dq will be 0 so we set q0 = q_cut and dq0_drho = dq0_dgradrho = 0 and go on
1107 : !! to the next point.
1108 : !! ------------------------------------------------------------------------------------
1109 : IF (total_rho(i_grid) < epsr) CYCLE
1110 : !! ------------------------------------------------------------------------------------
1111 : !! Calculate some intermediate values needed to find q
1112 : !! ------------------------------------------------------------------------------------
1113 : kF = (3.0_dp*pi*pi*total_rho(i_grid))**(1.0_dp/3.0_dp)
1114 : r_s = (3.0_dp/(4.0_dp*pi*total_rho(i_grid)))**(1.0_dp/3.0_dp)
1115 : sqrt_r_s = SQRT(r_s)
1116 :
1117 : gradient_correction = -Z_ab/(36.0_dp*kF*total_rho(i_grid)**2) &
1118 : *(gradient_rho(i_grid, 1)**2 + gradient_rho(i_grid, 2)**2 + gradient_rho(i_grid, 3)**2)
1119 :
1120 : LDA_1 = 8.0_dp*pi/3.0_dp*(LDA_A*(1.0_dp + LDA_a1*r_s))
1121 : LDA_2 = 2.0_dp*LDA_A*(LDA_b1*sqrt_r_s + LDA_b2*r_s + LDA_b3*r_s*sqrt_r_s + LDA_b4*r_s*r_s)
1122 : !! ---------------------------------------------------------------
1123 : !! This is the q value defined in equations 11 and 12 of DION
1124 : !! ---------------------------------------------------------------
1125 : q = kF + LDA_1*LOG(1.0_dp + 1.0_dp/LDA_2) + gradient_correction
1126 : !! ---------------------------------------------------------------
1127 : !! Here, we calculate q0 by saturating q according to equation 5 of SOLER. Also, we find
1128 : !! the derivative dq0_dq needed for the derivatives dq0_drho and dq0_dgradrh0 discussed below.
1129 : !! ---------------------------------------------------------------------------------------
1130 : q__q_cut = q/q_cut
1131 : CALL calculate_exponent_derivative(m_cut, q__q_cut, exponent, dq0_dq)
1132 : q0(i_grid) = q_cut*(1.0_dp - EXP(-exponent))
1133 : dq0_dq = dq0_dq*EXP(-exponent)
1134 : !! ---------------------------------------------------------------------------------------
1135 : !! This is to handle a case with q0 too small. We simply set it to the smallest q value in
1136 : !! out q_mesh. Hopefully this doesn't get used often (ever)
1137 : !! ---------------------------------------------------------------------------------------
1138 : IF (q0(i_grid) < q_min) THEN
1139 : q0(i_grid) = q_min
1140 : END IF
1141 : !! ---------------------------------------------------------------------------------------
1142 : !! Here we find derivatives. These are actually the density times the derivative of q0 with respect
1143 : !! to rho and gradient_rho. The density factor comes in since we are really differentiating
1144 : !! theta = (rho)*P(q0) with respect to density (or its gradient) which will be
1145 : !! dtheta_drho = P(q0) + dP_dq0 * [rho * dq0_dq * dq_drho] and
1146 : !! dtheta_dgradient_rho = dP_dq0 * [rho * dq0_dq * dq_dgradient_rho]
1147 : !! The parts in square brackets are what is calculated here. The dP_dq0 term will be interpolated
1148 : !! later. There should actually be a factor of the magnitude of the gradient in the gradient_rho derivative
1149 : !! but that cancels out when we differentiate the magnitude of the gradient with respect to a particular
1150 : !! component.
1151 : !! ------------------------------------------------------------------------------------------------
1152 :
1153 : dq0_drho(i_grid) = dq0_dq*(kF/3.0_dp - 7.0_dp/3.0_dp*gradient_correction &
1154 : - 8.0_dp*pi/9.0_dp*LDA_A*LDA_a1*r_s*LOG(1.0_dp + 1.0_dp/LDA_2) &
1155 : + LDA_1/(LDA_2*(1.0_dp + LDA_2)) &
1156 : *(2.0_dp*LDA_A*(LDA_b1/6.0_dp*sqrt_r_s + LDA_b2/3.0_dp*r_s + LDA_b3/2.0_dp*r_s*sqrt_r_s &
1157 : + 2.0_dp*LDA_b4/3.0_dp*r_s**2)))
1158 :
1159 : dq0_dgradrho(i_grid) = total_rho(i_grid)*dq0_dq*2.0_dp*(-Z_ab)/(36.0_dp*kF*total_rho(i_grid)**2)
1160 :
1161 : END DO
1162 : !$OMP END PARALLEL DO
1163 :
1164 254 : END SUBROUTINE get_q0_on_grid_vdw
1165 :
1166 : ! **************************************************************************************************
1167 : !> \brief ...
1168 : !> \param total_rho ...
1169 : !> \param gradient_rho ...
1170 : !> \param q0 ...
1171 : !> \param dq0_drho ...
1172 : !> \param dq0_dgradrho ...
1173 : !> \param dispersion_env ...
1174 : ! **************************************************************************************************
1175 954 : SUBROUTINE get_q0_on_grid_rvv10(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho, dispersion_env)
1176 : !!
1177 : !! more specifically it calculates the following
1178 : !!
1179 : !! q0(ir) = q0 as defined above
1180 : !! dq0_drho(ir) = total_rho * d q0 /d rho
1181 : !! dq0_dgradrho = total_rho / |gradient_rho| * d q0 / d |gradient_rho|
1182 : !!
1183 : REAL(dp), INTENT(IN) :: total_rho(:), gradient_rho(:, :)
1184 : REAL(dp), INTENT(OUT) :: q0(:), dq0_drho(:), dq0_dgradrho(:)
1185 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1186 :
1187 : INTEGER, PARAMETER :: m_cut = 12
1188 :
1189 : INTEGER :: i_grid
1190 : REAL(dp) :: b_value, C_value, dk_dn, dq0_dq, dw0_dn, &
1191 : exponent, gmod2, k, mod_grad, q, &
1192 : q__q_cut, q_cut, q_min, w0, wg2, wp2
1193 :
1194 954 : q_cut = dispersion_env%q_cut
1195 954 : q_min = dispersion_env%q_min
1196 954 : b_value = dispersion_env%b_value
1197 954 : C_value = dispersion_env%c_value
1198 :
1199 : !$OMP PARALLEL DO DEFAULT(NONE) &
1200 : !$OMP SHARED(total_rho, gradient_rho, q0, dq0_drho, dq0_dgradrho, q_cut, q_min, b_value, C_value) &
1201 : !$OMP PRIVATE(dk_dn, dq0_dq, dw0_dn, exponent, gmod2, k, mod_grad, q, q__q_cut, w0, wg2, wp2) &
1202 954 : !$OMP SCHEDULE(STATIC)
1203 : DO i_grid = 1, SIZE(total_rho)
1204 : q0(i_grid) = q_cut
1205 : dq0_drho(i_grid) = 0.0_dp
1206 : dq0_dgradrho(i_grid) = 0.0_dp
1207 :
1208 : gmod2 = gradient_rho(i_grid, 1)**2 + gradient_rho(i_grid, 2)**2 + gradient_rho(i_grid, 3)**2
1209 :
1210 : !if (total_rho(i_grid) > epsr .and. gmod2 > epsr) cycle
1211 : IF (total_rho(i_grid) > epsr) THEN
1212 :
1213 : !! Calculate some intermediate values needed to find q
1214 : !! ------------------------------------------------------------------------------------
1215 : mod_grad = SQRT(gmod2)
1216 :
1217 : wp2 = 16.0_dp*pi*total_rho(i_grid)
1218 : wg2 = 4_dp*C_value*(mod_grad/total_rho(i_grid))**4
1219 :
1220 : k = b_value*3.0_dp*pi*((total_rho(i_grid)/(9.0_dp*pi))**(1.0_dp/6.0_dp))
1221 : w0 = SQRT(wg2 + wp2/3.0_dp)
1222 :
1223 : q = w0/k
1224 :
1225 : !! Here, we calculate q0 by saturating q according
1226 : !! ---------------------------------------------------------------------------------------
1227 : q__q_cut = q/q_cut
1228 : CALL calculate_exponent_derivative(m_cut, q__q_cut, exponent, dq0_dq)
1229 : q0(i_grid) = q_cut*(1.0_dp - EXP(-exponent))
1230 : dq0_dq = dq0_dq*EXP(-exponent)
1231 :
1232 : !! ---------------------------------------------------------------------------------------
1233 : IF (q0(i_grid) < q_min) THEN
1234 : q0(i_grid) = q_min
1235 : END IF
1236 :
1237 : !!---------------------------------Final values---------------------------------
1238 : dw0_dn = 1.0_dp/(2.0_dp*w0)*(16.0_dp/3.0_dp*pi - 4.0_dp*wg2/total_rho(i_grid))
1239 : dk_dn = k/(6.0_dp*total_rho(i_grid))
1240 :
1241 : dq0_drho(i_grid) = dq0_dq*1.0_dp/(k**2)*(dw0_dn*k - dk_dn*w0)
1242 : ! wg2 is proportional to |gradient_rho|**4, so this limit is zero.
1243 : IF (gmod2 > 0.0_dp) THEN
1244 : dq0_dgradrho(i_grid) = dq0_dq*1.0_dp/(2.0_dp*k*w0)*4.0_dp*wg2/gmod2
1245 : END IF
1246 : END IF
1247 :
1248 : END DO
1249 : !$OMP END PARALLEL DO
1250 :
1251 954 : END SUBROUTINE get_q0_on_grid_rvv10
1252 :
1253 : ! **************************************************************************************************
1254 : !> \brief ...
1255 : !> \param total_rho ...
1256 : !> \param gradient_rho ...
1257 : !> \param q0 ...
1258 : !> \param dispersion_env ...
1259 : ! **************************************************************************************************
1260 0 : SUBROUTINE get_q0_on_grid_eo_vdw(total_rho, gradient_rho, q0, dispersion_env)
1261 :
1262 : REAL(dp), INTENT(IN) :: total_rho(:), gradient_rho(:, :)
1263 : REAL(dp), INTENT(OUT) :: q0(:)
1264 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1265 :
1266 : INTEGER, PARAMETER :: m_cut = 12
1267 : REAL(dp), PARAMETER :: LDA_A = 0.031091_dp, LDA_a1 = 0.2137_dp, LDA_b1 = 7.5957_dp, &
1268 : LDA_b2 = 3.5876_dp, LDA_b3 = 1.6382_dp, LDA_b4 = 0.49294_dp
1269 :
1270 : INTEGER :: i_grid
1271 : REAL(dp) :: exponent, gradient_correction, kF, &
1272 : LDA_1, LDA_2, q, q__q_cut, q_cut, &
1273 : q_min, r_s, sqrt_r_s, Z_ab
1274 :
1275 0 : q_cut = dispersion_env%q_cut
1276 0 : q_min = dispersion_env%q_min
1277 0 : SELECT CASE (dispersion_env%nl_type)
1278 : CASE DEFAULT
1279 0 : CPABORT("Unknown vdW-DF functional")
1280 : CASE (vdw_nl_DRSLL)
1281 0 : Z_ab = -0.8491_dp
1282 : CASE (vdw_nl_LMKLL)
1283 0 : Z_ab = -1.887_dp
1284 : END SELECT
1285 :
1286 : !$OMP PARALLEL DO DEFAULT(NONE) &
1287 : !$OMP SHARED(total_rho, gradient_rho, q0, q_cut, q_min, Z_ab) &
1288 : !$OMP PRIVATE(exponent, gradient_correction, kF, LDA_1, LDA_2, q, q__q_cut, r_s, sqrt_r_s) &
1289 0 : !$OMP SCHEDULE(STATIC)
1290 : DO i_grid = 1, SIZE(total_rho)
1291 : q0(i_grid) = q_cut
1292 : !! This prevents numerical problems. If the charge density is negative (an
1293 : !! unphysical situation), we simply treat it as very small. In that case,
1294 : !! q0 will be very large and will be saturated. For a saturated q0 the derivative
1295 : !! dq0_dq will be 0 so we set q0 = q_cut and dq0_drho = dq0_dgradrho = 0 and go on
1296 : !! to the next point.
1297 : !! ------------------------------------------------------------------------------------
1298 : IF (total_rho(i_grid) < epsr) CYCLE
1299 : !! ------------------------------------------------------------------------------------
1300 : !! Calculate some intermediate values needed to find q
1301 : !! ------------------------------------------------------------------------------------
1302 : kF = (3.0_dp*pi*pi*total_rho(i_grid))**(1.0_dp/3.0_dp)
1303 : r_s = (3.0_dp/(4.0_dp*pi*total_rho(i_grid)))**(1.0_dp/3.0_dp)
1304 : sqrt_r_s = SQRT(r_s)
1305 :
1306 : gradient_correction = -Z_ab/(36.0_dp*kF*total_rho(i_grid)**2) &
1307 : *(gradient_rho(i_grid, 1)**2 + gradient_rho(i_grid, 2)**2 + gradient_rho(i_grid, 3)**2)
1308 :
1309 : LDA_1 = 8.0_dp*pi/3.0_dp*(LDA_A*(1.0_dp + LDA_a1*r_s))
1310 : LDA_2 = 2.0_dp*LDA_A*(LDA_b1*sqrt_r_s + LDA_b2*r_s + LDA_b3*r_s*sqrt_r_s + LDA_b4*r_s*r_s)
1311 : !! ------------------------------------------------------------------------------------
1312 : !! This is the q value defined in equations 11 and 12 of DION
1313 : !! ---------------------------------------------------------------
1314 : q = kF + LDA_1*LOG(1.0_dp + 1.0_dp/LDA_2) + gradient_correction
1315 :
1316 : !! ---------------------------------------------------------------
1317 : !! Here, we calculate q0 by saturating q according to equation 5 of SOLER. Also, we find
1318 : !! the derivative dq0_dq needed for the derivatives dq0_drho and dq0_dgradrh0 discussed below.
1319 : !! ---------------------------------------------------------------------------------------
1320 : q__q_cut = q/q_cut
1321 : CALL calculate_exponent(m_cut, q__q_cut, exponent)
1322 : q0(i_grid) = q_cut*(1.0_dp - EXP(-exponent))
1323 :
1324 : !! ---------------------------------------------------------------------------------------
1325 : !! This is to handle a case with q0 too small. We simply set it to the smallest q value in
1326 : !! out q_mesh. Hopefully this doesn't get used often (ever)
1327 : !! ---------------------------------------------------------------------------------------
1328 : IF (q0(i_grid) < q_min) THEN
1329 : q0(i_grid) = q_min
1330 : END IF
1331 : END DO
1332 : !$OMP END PARALLEL DO
1333 :
1334 0 : END SUBROUTINE get_q0_on_grid_eo_vdw
1335 :
1336 : ! **************************************************************************************************
1337 : !> \brief ...
1338 : !> \param total_rho ...
1339 : !> \param gradient_rho ...
1340 : !> \param q0 ...
1341 : !> \param dispersion_env ...
1342 : ! **************************************************************************************************
1343 6 : SUBROUTINE get_q0_on_grid_eo_rvv10(total_rho, gradient_rho, q0, dispersion_env)
1344 :
1345 : REAL(dp), INTENT(IN) :: total_rho(:), gradient_rho(:, :)
1346 : REAL(dp), INTENT(OUT) :: q0(:)
1347 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1348 :
1349 : INTEGER, PARAMETER :: m_cut = 12
1350 :
1351 : INTEGER :: i_grid
1352 : REAL(dp) :: b_value, C_value, exponent, gmod2, k, q, &
1353 : q__q_cut, q_cut, q_min, w0, wg2, wp2
1354 :
1355 6 : q_cut = dispersion_env%q_cut
1356 6 : q_min = dispersion_env%q_min
1357 6 : b_value = dispersion_env%b_value
1358 6 : C_value = dispersion_env%c_value
1359 :
1360 : !$OMP PARALLEL DO DEFAULT(NONE) &
1361 : !$OMP SHARED(total_rho, gradient_rho, q0, q_cut, q_min, b_value, C_value) &
1362 : !$OMP PRIVATE(exponent, gmod2, k, q, q__q_cut, w0, wg2, wp2) &
1363 6 : !$OMP SCHEDULE(STATIC)
1364 : DO i_grid = 1, SIZE(total_rho)
1365 : q0(i_grid) = q_cut
1366 :
1367 : gmod2 = gradient_rho(i_grid, 1)**2 + gradient_rho(i_grid, 2)**2 + gradient_rho(i_grid, 3)**2
1368 :
1369 : !if (total_rho(i_grid) > epsr .and. gmod2 > epsr) cycle
1370 : IF (total_rho(i_grid) > epsr) THEN
1371 :
1372 : !! Calculate some intermediate values needed to find q
1373 : !! ------------------------------------------------------------------------------------
1374 : wp2 = 16.0_dp*pi*total_rho(i_grid)
1375 : wg2 = 4_dp*C_value*(gmod2*gmod2)/(total_rho(i_grid)**4)
1376 :
1377 : k = b_value*3.0_dp*pi*((total_rho(i_grid)/(9.0_dp*pi))**(1.0_dp/6.0_dp))
1378 : w0 = SQRT(wg2 + wp2/3.0_dp)
1379 :
1380 : q = w0/k
1381 :
1382 : !! Here, we calculate q0 by saturating q according
1383 : !! ---------------------------------------------------------------------------------------
1384 : q__q_cut = q/q_cut
1385 : CALL calculate_exponent(m_cut, q__q_cut, exponent)
1386 : q0(i_grid) = q_cut*(1.0_dp - EXP(-exponent))
1387 :
1388 : IF (q0(i_grid) < q_min) THEN
1389 : q0(i_grid) = q_min
1390 : END IF
1391 :
1392 : END IF
1393 :
1394 : END DO
1395 : !$OMP END PARALLEL DO
1396 :
1397 6 : END SUBROUTINE get_q0_on_grid_eo_rvv10
1398 :
1399 : ! **************************************************************************************************
1400 : !> \brief Prepare the spline intervals, weights and density factors for streamed theta construction.
1401 : !> \param q0 Saturated interpolation points
1402 : !> \param rho Total density
1403 : !> \param dispersion_env Non-local functional parameters
1404 : !> \param q_low Lower spline endpoint; zero denotes an inactive rVV10 point
1405 : !> \param coeff Cubic spline coefficients a, b, c, d
1406 : !> \param theta_scale Density factor multiplying the interpolated spline
1407 : ! **************************************************************************************************
1408 1214 : SUBROUTINE prepare_splines(q0, rho, dispersion_env, q_low, coeff, theta_scale)
1409 : REAL(dp), INTENT(IN) :: q0(:), rho(:)
1410 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1411 : INTEGER, INTENT(OUT) :: q_low(:)
1412 : REAL(dp), INTENT(OUT) :: coeff(:, :), theta_scale(:)
1413 :
1414 : INTEGER :: i, j, lower, nqs, upper
1415 : LOGICAL :: rvv10
1416 : REAL(dp) :: a, b, const, dx, dx2_6
1417 : REAL(dp), POINTER :: q_mesh(:)
1418 :
1419 1214 : q_mesh => dispersion_env%q_mesh
1420 1214 : nqs = dispersion_env%nqs
1421 1214 : CPASSERT(nqs >= 2)
1422 1214 : rvv10 = dispersion_env%nl_type == vdw_nl_RVV10
1423 1214 : const = 1.0_dp/(3.0_dp*rootpi*dispersion_env%b_value**1.5_dp)/(pi**0.75_dp)
1424 : !$OMP PARALLEL DO DEFAULT(NONE) &
1425 : !$OMP SHARED(q0, rho, q_mesh, nqs, rvv10, const, q_low, coeff, theta_scale) &
1426 1214 : !$OMP PRIVATE(j, lower, upper, A, b, dx, dx2_6) SCHEDULE(STATIC)
1427 : DO i = 1, SIZE(q0)
1428 : IF (rvv10 .AND. rho(i) <= epsr) THEN
1429 : q_low(i) = 0
1430 : coeff(i, :) = 0.0_dp
1431 : theta_scale(i) = 0.0_dp
1432 : CYCLE
1433 : END IF
1434 : lower = 1
1435 : upper = nqs
1436 : DO WHILE (upper - lower > 1)
1437 : j = (upper + lower)/2
1438 : IF (q0(i) > q_mesh(j)) THEN
1439 : lower = j
1440 : ELSE
1441 : upper = j
1442 : END IF
1443 : END DO
1444 : q_low(i) = lower
1445 : dx = q_mesh(upper) - q_mesh(lower)
1446 : dx2_6 = dx*dx/6.0_dp
1447 : a = (q_mesh(upper) - q0(i))/dx
1448 : b = (q0(i) - q_mesh(lower))/dx
1449 : coeff(i, 1) = a
1450 : coeff(i, 2) = b
1451 : coeff(i, 3) = (a**3 - a)*dx2_6
1452 : coeff(i, 4) = (b**3 - b)*dx2_6
1453 : IF (rvv10) THEN
1454 : theta_scale(i) = const*rho(i)**0.75_dp
1455 : ELSE
1456 : theta_scale(i) = rho(i)
1457 : END IF
1458 : END DO
1459 : !$OMP END PARALLEL DO
1460 1214 : END SUBROUTINE prepare_splines
1461 :
1462 : ! **************************************************************************************************
1463 : !> \brief Generate one real-space theta channel directly in the existing FFT workspace.
1464 : !> \param iq Channel index
1465 : !> \param q_low Lower spline endpoint
1466 : !> \param coeff Spline coefficients
1467 : !> \param theta_scale Density factor
1468 : !> \param dispersion_env Non-local functional parameters
1469 : !> \param theta FFT input, viewed as a contiguous one-dimensional array
1470 : ! **************************************************************************************************
1471 24280 : SUBROUTINE build_theta(iq, q_low, coeff, theta_scale, dispersion_env, theta)
1472 : INTEGER, INTENT(IN) :: iq, q_low(:)
1473 : REAL(dp), INTENT(IN) :: coeff(:, :), theta_scale(:)
1474 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1475 : REAL(dp), INTENT(OUT) :: theta(:)
1476 :
1477 : INTEGER :: i, lower
1478 : REAL(dp) :: p
1479 : REAL(dp), POINTER :: d2y(:, :)
1480 :
1481 24280 : d2y => dispersion_env%d2y_dx2
1482 : !$OMP PARALLEL DO SIMD DEFAULT(NONE) &
1483 24280 : !$OMP SHARED(iq, q_low, coeff, theta_scale, d2y, theta) PRIVATE(lower, p) SCHEDULE(STATIC)
1484 : DO i = 1, SIZE(theta)
1485 : lower = q_low(i)
1486 : theta(i) = 0.0_dp
1487 : IF (lower == 0) CYCLE
1488 : p = coeff(i, 1)*MERGE(1.0_dp, 0.0_dp, iq == lower) &
1489 : + coeff(i, 2)*MERGE(1.0_dp, 0.0_dp, iq == lower + 1) &
1490 : + (coeff(i, 3)*d2y(iq, lower) + coeff(i, 4)*d2y(iq, lower + 1))
1491 : theta(i) = p*theta_scale(i)
1492 : END DO
1493 : !$OMP END PARALLEL DO SIMD
1494 24280 : END SUBROUTINE build_theta
1495 :
1496 : ! **************************************************************************************************
1497 : !> \brief Accumulate one inverse-transformed channel without storing a real-space channel matrix.
1498 : !> \param iq Channel index
1499 : !> \param q_low Lower spline endpoint, using the potential-side knot convention
1500 : !> \param dispersion_env Non-local functional parameters
1501 : !> \param u Inverse FFT output
1502 : !> \param u_contract Sums against the two second-derivative columns, then the two endpoint values
1503 : ! **************************************************************************************************
1504 24160 : SUBROUTINE accumulate_potential(iq, q_low, dispersion_env, u, u_contract)
1505 : INTEGER, INTENT(IN) :: iq, q_low(:)
1506 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1507 : REAL(dp), INTENT(IN) :: u(:)
1508 : REAL(dp), INTENT(INOUT) :: u_contract(:, :)
1509 :
1510 : INTEGER :: i, lower
1511 : REAL(dp), POINTER :: d2y(:, :)
1512 :
1513 24160 : d2y => dispersion_env%d2y_dx2
1514 : !$OMP PARALLEL DO SIMD DEFAULT(NONE) &
1515 24160 : !$OMP SHARED(iq, q_low, d2y, u, u_contract) PRIVATE(lower) SCHEDULE(STATIC)
1516 : DO i = 1, SIZE(u)
1517 : lower = q_low(i)
1518 : IF (lower == 0) CYCLE
1519 : u_contract(i, 1) = u_contract(i, 1) + u(i)*d2y(iq, lower)
1520 : u_contract(i, 2) = u_contract(i, 2) + u(i)*d2y(iq, lower + 1)
1521 : IF (iq == lower) u_contract(i, 3) = u(i)
1522 : IF (iq == lower + 1) u_contract(i, 4) = u(i)
1523 : END DO
1524 : !$OMP END PARALLEL DO SIMD
1525 24160 : END SUBROUTINE accumulate_potential
1526 :
1527 : ! **************************************************************************************************
1528 : !> \brief This routine is modeled after an algorithm from "Numerical Recipes in C" by Cambridge
1529 : !> University Press, pages 96-97. It was adapted for Fortran and for the problem at hand.
1530 : !> \param x ...
1531 : !> \param d2y_dx2 ...
1532 : !> \par History
1533 : !> OpenMP added: Aug 2016 MTucker
1534 : ! **************************************************************************************************
1535 66 : SUBROUTINE initialize_spline_interpolation(x, d2y_dx2)
1536 :
1537 : REAL(dp), INTENT(in) :: x(:)
1538 : REAL(dp), INTENT(inout) :: d2y_dx2(:, :)
1539 :
1540 : INTEGER :: index, Nx, P_i
1541 : REAL(dp) :: temp1, temp2
1542 66 : REAL(dp), ALLOCATABLE :: temp_array(:), y(:)
1543 :
1544 66 : Nx = SIZE(x)
1545 :
1546 : !$OMP PARALLEL DEFAULT( NONE ) &
1547 : !$OMP SHARED( x, d2y_dx2, Nx ) &
1548 : !$OMP PRIVATE( temp_array, y &
1549 : !$OMP , index, temp1, temp2 &
1550 66 : !$OMP )
1551 :
1552 : ALLOCATE (temp_array(Nx), y(Nx))
1553 :
1554 : !$OMP DO
1555 : DO P_i = 1, Nx
1556 : !! In the Soler method, the polynomials that are interpolated are Kronecker delta functions
1557 : !! at a particular q point. So, we set all y values to 0 except the one corresponding to
1558 : !! the particular function P_i.
1559 : !! ----------------------------------------------------------------------------------------
1560 : y = 0.0_dp
1561 : y(P_i) = 1.0_dp
1562 : !! ----------------------------------------------------------------------------------------
1563 :
1564 : d2y_dx2(P_i, 1) = 0.0_dp
1565 : temp_array(1) = 0.0_dp
1566 : DO index = 2, Nx - 1
1567 : temp1 = (x(index) - x(index - 1))/(x(index + 1) - x(index - 1))
1568 : temp2 = temp1*d2y_dx2(P_i, index - 1) + 2.0_dp
1569 : d2y_dx2(P_i, index) = (temp1 - 1.0_dp)/temp2
1570 : temp_array(index) = (y(index + 1) - y(index))/(x(index + 1) - x(index)) &
1571 : - (y(index) - y(index - 1))/(x(index) - x(index - 1))
1572 : temp_array(index) = (6.0_dp*temp_array(index)/(x(index + 1) - x(index - 1)) &
1573 : - temp1*temp_array(index - 1))/temp2
1574 : END DO
1575 : d2y_dx2(P_i, Nx) = 0.0_dp
1576 : DO index = Nx - 1, 1, -1
1577 : d2y_dx2(P_i, index) = d2y_dx2(P_i, index)*d2y_dx2(P_i, index + 1) + temp_array(index)
1578 : END DO
1579 : END DO
1580 : !$OMP END DO
1581 :
1582 : DEALLOCATE (temp_array, y)
1583 : !$OMP END PARALLEL
1584 :
1585 66 : END SUBROUTINE initialize_spline_interpolation
1586 :
1587 : ! **************************************************************************************************
1588 : !> \brief Interpolate the symmetric kernel and optionally its radial derivative from packed tables.
1589 : !> \param k Reciprocal-vector length
1590 : !> \param kernel_of_k Interpolated kernel matrix
1591 : !> \param dispersion_env Radial mesh and channel count
1592 : !> \param kernel_table Packed channel pairs, radial points, and value/second derivative
1593 : !> \param dkernel_of_dk Optional derivative matrix for the kernel virial
1594 : ! **************************************************************************************************
1595 927552 : SUBROUTINE interpolate_kernel(k, kernel_of_k, dispersion_env, kernel_table, dkernel_of_dk)
1596 : REAL(dp), INTENT(IN) :: k
1597 : REAL(dp), INTENT(OUT) :: kernel_of_k(:, :)
1598 : TYPE(qs_dispersion_type), POINTER :: dispersion_env
1599 : REAL(dp), INTENT(IN) :: kernel_table(:, 0:, :)
1600 : REAL(dp), INTENT(OUT), OPTIONAL :: dkernel_of_dk(:, :)
1601 :
1602 : INTEGER :: ipair, k_i, q1_i, q2_i
1603 : LOGICAL :: on_mesh
1604 : REAL(dp) :: a, b, c, d, da, db, dc, dd, dk, dk_6, &
1605 : value
1606 :
1607 927552 : dk = dispersion_env%dk
1608 927552 : CPASSERT(k < dispersion_env%nr_points*dk)
1609 927552 : k_i = INT(k/dk)
1610 927552 : on_mesh = MOD(k, dk) == 0.0_dp
1611 927552 : a = (dk*(k_i + 1.0_dp) - k)/dk
1612 927552 : b = (k - dk*k_i)/dk
1613 927552 : c = (a**3 - a)*(dk*dk/6.0_dp)
1614 927552 : d = (b**3 - b)*(dk*dk/6.0_dp)
1615 19478592 : DO q1_i = 1, dispersion_env%nqs
1616 19478592 : !$OMP SIMD PRIVATE(ipair, value)
1617 : DO q2_i = 1, q1_i
1618 194785920 : ipair = q1_i*(q1_i - 1)/2 + q2_i
1619 194785920 : IF (on_mesh) THEN
1620 127470 : value = kernel_table(ipair, k_i, 1)
1621 : ELSE
1622 : value = a*kernel_table(ipair, k_i, 1) + b*kernel_table(ipair, k_i + 1, 1) &
1623 194658450 : + (c*kernel_table(ipair, k_i, 2) + d*kernel_table(ipair, k_i + 1, 2))
1624 : END IF
1625 194785920 : kernel_of_k(q2_i, q1_i) = value
1626 194785920 : kernel_of_k(q1_i, q2_i) = value
1627 : END DO
1628 : !$OMP END SIMD
1629 : END DO
1630 927552 : IF (PRESENT(dkernel_of_dk)) THEN
1631 425731 : dk_6 = dk/6.0_dp
1632 425731 : da = -1.0_dp/dk
1633 425731 : db = 1.0_dp/dk
1634 425731 : dc = -(3*a**2 - 1.0_dp)*dk_6
1635 425731 : dd = (3*b**2 - 1.0_dp)*dk_6
1636 8940351 : DO q1_i = 1, dispersion_env%nqs
1637 8940351 : !$OMP SIMD PRIVATE(ipair, value)
1638 : DO q2_i = 1, q1_i
1639 89403510 : ipair = q1_i*(q1_i - 1)/2 + q2_i
1640 : value = da*kernel_table(ipair, k_i, 1) + db*kernel_table(ipair, k_i + 1, 1) &
1641 89403510 : + dc*kernel_table(ipair, k_i, 2) + dd*kernel_table(ipair, k_i + 1, 2)
1642 89403510 : dkernel_of_dk(q2_i, q1_i) = value
1643 89403510 : dkernel_of_dk(q1_i, q2_i) = value
1644 : END DO
1645 : !$OMP END SIMD
1646 : END DO
1647 : END IF
1648 927552 : END SUBROUTINE interpolate_kernel
1649 :
1650 : ! **************************************************************************************************
1651 :
1652 : END MODULE qs_dispersion_nonloc
|