(git:08825b9)
Loading...
Searching...
No Matches
pw_unittest.F
Go to the documentation of this file.
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
11 USE kinds, ONLY: dp
12 USE libcp2k, ONLY: cp2k_finalize,&
14 USE mathconstants, ONLY: fourpi,&
15 pi
16 USE message_passing, ONLY: mp_comm_type,&
20 USE pw_copy_all, ONLY: pw_copy_match
21 USE pw_grid_types, ONLY: fullspace,&
22 halfspace,&
24 USE pw_grids, ONLY: pw_grid_create,&
26 USE pw_methods, ONLY: do_accurate_sum,&
34 USE pw_types, ONLY: pw_c1d_gs_type,&
39
40 IMPLICIT NONE
41
42 INTEGER :: mode
43 REAL(dp) :: max_error
44 TYPE(cp_logger_type), POINTER :: logger
45 TYPE(mp_para_env_type), POINTER :: para_env
46
47 CALL cp2k_init()
48 logger => cp_get_default_logger()
49 para_env => logger%para_env
51 DO mode = 0, 8
52 CALL check_copy(mode)
53 END DO
54 max_error = 0.0_dp
55 CALL check_pw_integral_and_poisson()
56 CALL check_mt2d_kernel()
57 print '(A,ES16.7)', 'PW integral and MT2D kernel maximum scaled error: ', max_error
58 CALL cp2k_finalize()
59
60CONTAINS
61
62! **************************************************************************************************
63!> \brief Preserve three-digit cube exponents and parse adjacent values across z-slices.
64! **************************************************************************************************
65 SUBROUTINE check_cube_values()
66 CHARACTER(LEN=13) :: value
67 CHARACTER(LEN=79) :: values
68 CHARACTER(LEN=92) :: slice_values
69 REAL(KIND=dp), DIMENSION(6) :: buffer, reference
70 REAL(KIND=dp), DIMENSION(7) :: slice_buffer, slice_reference
71
72 WRITE (value, cube_value_format) 3.33004e-101_dp
73 IF (index(value, "E-101") <= 0) THEN
74 error stop "Cube value format must preserve three-digit negative exponents."
75 END IF
76
77 reference = [0.0_dp, -0.91458e-35_dp, 3.33004e-101_dp, &
78 -1.32487e-103_dp, 1.0_dp, -1.0_dp]
79 WRITE (values(1:78), cube_values_format) reference
80 values(79:79) = new_line("C")
81 IF (index(values, "E-101") <= 0) THEN
82 error stop "Cube line format must preserve E-101."
83 END IF
84 IF (index(values, "E-103") <= 0) THEN
85 error stop "Cube line format must preserve E-103."
86 END IF
87 CALL cube_read_values(values, buffer)
88 IF (maxval(abs(buffer - reference)) > 1.0e-12_dp) THEN
89 error stop "Cube reader must parse adjacent explicit-exponent values."
90 END IF
91
92 slice_reference(1:6) = reference
93 slice_reference(7) = 0.27183e-123_dp
94 WRITE (slice_values(1:78), cube_values_format) slice_reference(1:6)
95 slice_values(79:79) = new_line("C")
96 WRITE (slice_values(80:92), cube_value_format) slice_reference(7)
97 CALL cube_read_values(slice_values, slice_buffer)
98 IF (maxval(abs(slice_buffer - slice_reference)) > 1.0e-12_dp) THEN
99 error stop "Cube reader must parse multi-line z-slices."
100 END IF
101 END SUBROUTINE check_cube_values
102
103! **************************************************************************************************
104!> \brief Compare identical, permuted, redistributed, partial and empty PW grids.
105!> \param mode Layout variant; mode two changes only one rank's ordering.
106!> \author Thomas D. Kuehne (tkuehne@cp2k.org)
107! **************************************************************************************************
108 SUBROUTINE check_copy(mode)
109 INTEGER, INTENT(IN) :: mode
110
111 COMPLEX(dp), PARAMETER :: untouched = (-999.0_dp, 17.0_dp)
112
113 COMPLEX(dp) :: expected
114 INTEGER :: dimensions(1), i, j, nsource, ntarget, &
115 owner, rank
116 TYPE(pw_c1d_gs_type) :: destination, source
117 TYPE(pw_grid_type), TARGET :: source_grid, target_grid
118
119 rank = para_env%mepos
120 nsource = 7
121 IF (mode == 6 .OR. (mode >= 7 .AND. rank == 0)) nsource = 0
122 owner = rank
123 IF (mode == 3 .OR. mode == 8) owner = mod(rank + 1, para_env%num_pe)
124 ntarget = nsource
125 IF (mode == 4) ntarget = nsource - 1
126 IF (mode == 5) ntarget = nsource + 1
127 IF (mode == 8) ntarget = merge(0, 7, owner == 0)
128 ALLOCATE (source_grid%g_hat(3, nsource), target_grid%g_hat(3, ntarget))
129 ALLOCATE (source%array(nsource), destination%array(ntarget))
130 source%pw_grid => source_grid
131 destination%pw_grid => target_grid
132 dimensions = [para_env%num_pe]
133 CALL source_grid%para%group%create(para_env, 1, dimensions)
134 target_grid%para%group = source_grid%para%group
135 source_grid%ngpts_cut_local = nsource
136 target_grid%ngpts_cut_local = ntarget
137 DO i = 1, nsource
138 source_grid%g_hat(:, i) = [i, rank, 0]
139 source%array(i) = cmplx(i + 11*rank, -i - 3*rank, dp)
140 END DO
141 DO i = 1, ntarget
142 j = i
143 IF (mode == 1 .OR. (mode == 2 .AND. rank == 0)) j = ntarget + 1 - i
144 target_grid%g_hat(:, i) = [j, owner, 0]
145 END DO
146 destination%array(:) = untouched
147 CALL pw_copy_match(source, destination)
148 DO i = 1, ntarget
149 j = target_grid%g_hat(1, i)
150 expected = cmplx(j + 11*owner, -j - 3*owner, dp)
151 IF (mode == 5 .AND. j > nsource) expected = untouched
152 IF (abs(destination%array(i) - expected) > 1.e-14_dp) error stop 'Incorrect PW copy'
153 END DO
154 DEALLOCATE (source%array, destination%array)
155 CALL source_grid%para%group%free()
156 END SUBROUTINE check_copy
157
158! **************************************************************************************************
159!> \brief Check PW multiplicity and volume normalization, and auxiliary-density Poisson routes.
160! **************************************************************************************************
161 SUBROUTINE check_pw_integral_and_poisson()
162 REAL(dp), PARAMETER :: step = 5.e-4_dp, &
163 strain_steps(4) = [step, -step, 2.0_dp*step, -2.0_dp*step]
164
165 COMPLEX(dp) :: a_mode, b_mode
166 INTEGER :: axis, ig, sign_step, span, sumtype, test
167 REAL(dp) :: cell(3, 3), derivative, energy, energy_dv, energy_v, energy_v_dv, expected, &
168 expected_real, kernel, reference, shifted_g(3), strain_energy(4), stress(3, 3), &
169 stress_dv(3, 3), stress_only(3, 3), stress_v(3, 3), stress_v_dv(3, 3)
170 TYPE(mp_comm_type) :: comm
171 TYPE(pw_c1d_gs_type) :: a, b, gradient(3), gradient_v_dv(3), &
172 potential, potential_v_dv
173 TYPE(pw_grid_type), POINTER :: grid
174 TYPE(pw_poisson_type) :: poisson
175 TYPE(pw_r1d_gs_type) :: a_real, b_real
176
177 CALL comm%set_handle(mp_comm_world%get_handle())
178 ! Nonunit volume and an appreciable truncation factor exp(-pi).
179 cell = 0.0_dp
180 DO axis = 1, 3
181 cell(axis, axis) = 7.0_dp**(1.0_dp/3.0_dp)
182 END DO
183 a_mode = cmplx(0.4_dp, -0.2_dp, dp)
184 b_mode = cmplx(-0.7_dp, 0.9_dp, dp)
185 NULLIFY (grid)
186 DO span = fullspace, halfspace, -1
187 ! Reciprocal inputs and outputs need no FFT. The production grid still
188 ! supplies the distributed G0 ownership and half/full-space mappings.
189 CALL pw_grid_create(grid, comm, cell, grid_span=span, npts=[5, 5, 5], odd=.true., fft_usage=.false.)
190 CALL a%create(grid)
191 CALL b%create(grid)
192 CALL a_real%create(grid)
193 CALL b_real%create(grid)
194 CALL potential%create(grid)
195 CALL potential_v_dv%create(grid)
196 DO axis = 1, 3
197 CALL gradient(axis)%create(grid)
198 CALL gradient_v_dv(axis)%create(grid)
199 END DO
200 ALLOCATE (poisson%pw_pools(1))
201 CALL pw_pool_create(poisson%pw_pools(1)%pool, grid)
202 poisson%pw_level = 1
203 poisson%parameters%solver = pw_poisson_mt
204 poisson%parameters%periodic = [1, 1, 0]
205 poisson%parameters%mt_alpha = 1.0_dp
206 CALL pw_poisson_set(poisson, cell_hmat=cell, use_level=1)
207 DO test = 1, 2
208 ! Constant G0 only and G0 plus a Hermitian mode pair.
209 a%array = 0.0_dp
210 b%array = 0.0_dp
211 expected = 0.0_dp
212 expected_real = 0.0_dp
213 reference = 0.0_dp
214 IF (grid%have_g0) THEN
215 a%array(1) = 2.0_dp
216 b%array(1) = 3.0_dp
217 END IF
218 expected = 6.0_dp
219 expected_real = 6.0_dp
220 reference = 6.0_dp*mt2d_kernel_reference([0.0_dp, 0.0_dp, 0.0_dp], 3, cell(3, 3))
221 IF (test /= 1) THEN
222 DO ig = 1, grid%ngpts_cut_local
223 IF (any(grid%g_hat(2:3, ig) /= 0)) cycle
224 IF (grid%g_hat(1, ig) == 1) THEN
225 a%array(ig) = a_mode
226 b%array(ig) = b_mode
227 ELSE IF (grid%g_hat(1, ig) == -1) THEN
228 a%array(ig) = conjg(a_mode)
229 b%array(ig) = conjg(b_mode)
230 END IF
231 END DO
232 expected = expected + 2.0_dp*real(conjg(a_mode)*b_mode, dp)
233 expected_real = expected_real + 2.0_dp*real(a_mode, dp)*real(b_mode, dp)
234 kernel = mt2d_kernel_reference([2.0_dp*pi/cell(1, 1), 0.0_dp, 0.0_dp], 3, cell(3, 3))
235 reference = reference + 2.0_dp*real(conjg(a_mode)*b_mode, dp)*kernel
236 END IF
237 a_real%array = real(a%array, dp)
238 b_real%array = real(b%array, dp)
239 ! G0 must have weight one, the mode pair weight two, and both must
240 ! receive V=7 for an integral. just_sum retains the unscaled oracle.
241 DO sumtype = do_accurate_sum, do_standard_sum
242 CALL check_error(pw_integral_ab(a, b, sumtype=sumtype), 7.0_dp*expected, 3.e-14_dp)
243 CALL check_error(pw_integral_ab(a, b, sumtype=sumtype, just_sum=.true.), expected, 3.e-14_dp)
244 CALL check_error(pw_integral_ab(a_real, b_real, sumtype=sumtype), 7.0_dp*expected_real, 3.e-14_dp)
245 CALL check_error(pw_integral_ab(a_real, b_real, sumtype=sumtype, just_sum=.true.), &
246 expected_real, 3.e-14_dp)
247 CALL check_error(pw_integral_ab(a, b_real, sumtype=sumtype), 7.0_dp*expected_real, 3.e-14_dp)
248 CALL check_error(pw_integral_ab(a_real, b, sumtype=sumtype), 7.0_dp*expected_real, 3.e-14_dp)
249 END DO
250 CALL pw_poisson_solve(poisson, a, energy, h_stress=stress, aux_density=b)
251 CALL pw_poisson_solve(poisson, a, energy_dv, dvhartree=gradient, h_stress=stress_dv, aux_density=b)
252 CALL pw_poisson_solve(poisson, a, energy_v, potential, h_stress=stress_v, aux_density=b)
253 CALL pw_poisson_solve(poisson, a, energy_v_dv, potential_v_dv, dvhartree=gradient_v_dv, &
254 h_stress=stress_v_dv, aux_density=b)
255 CALL pw_poisson_solve(poisson, a, h_stress=stress_only, aux_density=b)
256 CALL check_error(energy, 0.5_dp*7.0_dp*reference, 3.e-13_dp)
257 CALL check_error(energy_dv, energy, 3.e-13_dp)
258 CALL check_error(energy_v, energy, 3.e-13_dp)
259 CALL check_error(energy_v_dv, energy, 3.e-13_dp)
260 CALL check_error(maxval(abs(stress_dv - stress)), 0.0_dp, 3.e-11_dp)
261 CALL check_error(maxval(abs(stress_v - stress)), 0.0_dp, 3.e-11_dp)
262 CALL check_error(maxval(abs(stress_v_dv - stress)), 0.0_dp, 3.e-11_dp)
263 CALL check_error(maxval(abs(stress_only - stress)), 0.0_dp, 3.e-11_dp)
264 IF (test == 2) THEN
265 ! Fixed Fourier charges Q=V*rho give E(epsilon)=V/2/(1+epsilon)
266 ! times the strained kernel sum. This absolute production-virial
267 ! oracle includes both G0/volume and the nonzero-mode kernel term.
268 DO sign_step = 1, 4
269 shifted_g = [2.0_dp*pi/cell(1, 1)/(1.0_dp + strain_steps(sign_step)), 0.0_dp, 0.0_dp]
270 reference = 6.0_dp*mt2d_kernel_reference([0.0_dp, 0.0_dp, 0.0_dp], 3, cell(3, 3)) &
271 + 2.0_dp*real(conjg(a_mode)*b_mode, dp)*mt2d_kernel_reference(shifted_g, 3, cell(3, 3))
272 strain_energy(sign_step) = 0.5_dp*7.0_dp*reference/(1.0_dp + strain_steps(sign_step))
273 END DO
274 derivative = (8.0_dp*(strain_energy(1) - strain_energy(2)) &
275 - strain_energy(3) + strain_energy(4))/(12.0_dp*step)
276 CALL check_error(stress(1, 1) + derivative, 0.0_dp, 3.e-9_dp)
277 END IF
278 DO ig = 1, grid%ngpts_cut_local
279 kernel = mt2d_kernel_reference(grid%g(:, ig), 3, cell(3, 3))
280 CALL check_error(abs(potential%array(ig) - kernel*a%array(ig)), 0.0_dp, 3.e-13_dp)
281 CALL check_error(abs(potential_v_dv%array(ig) - kernel*a%array(ig)), 0.0_dp, 3.e-13_dp)
282 DO axis = 1, 3
283 CALL check_error(abs(gradient(axis)%array(ig) &
284 - cmplx(0.0_dp, grid%g(axis, ig), dp)*kernel*a%array(ig)), 0.0_dp, 3.e-13_dp)
285 CALL check_error(abs(gradient_v_dv(axis)%array(ig) &
286 - cmplx(0.0_dp, grid%g(axis, ig), dp)*kernel*a%array(ig)), 0.0_dp, 3.e-13_dp)
287 END DO
288 END DO
289 END DO
290 CALL poisson%release()
291 CALL a%release()
292 CALL b%release()
293 CALL a_real%release()
294 CALL b_real%release()
295 CALL potential%release()
296 CALL potential_v_dv%release()
297 DO axis = 1, 3
298 CALL gradient(axis)%release()
299 CALL gradient_v_dv(axis)%release()
300 END DO
301 CALL pw_grid_release(grid)
302 END DO
303 END SUBROUTINE check_pw_integral_and_poisson
304
305! **************************************************************************************************
306!> \brief Compare the MT2D kernel and in-plane virial with independent strained-cell energies.
307! **************************************************************************************************
308 SUBROUTINE check_mt2d_kernel()
309 REAL(dp), PARAMETER :: step = 3.e-4_dp, shifts(4) = [step, -step, 2.0_dp*step, -2.0_dp*step]
310
311 INTEGER :: alpha, beta, ip, iz, jp, miller(3), &
312 mode, modes(3, 5), sign_step
313 REAL(dp) :: derivative, g(3), h_inv(3, 3), kernel, &
314 lengths(3), shifted_g(3), &
315 shifted_kernel(4), strain(3, 3)
316
317 ! Rotate the Cartesian vacuum axis. A shear within the periodic plane is
318 ! permitted; only normal/periodic mixing is excluded by the MT2D kernel.
319 DO iz = 1, 3
320 ip = mod(iz, 3) + 1
321 jp = mod(iz + 1, 3) + 1
322 lengths = [9.0_dp, 13.0_dp, 17.0_dp]
323 h_inv = 0.0_dp
324 DO alpha = 1, 3
325 h_inv(alpha, alpha) = 1.0_dp/lengths(alpha)
326 END DO
327 h_inv(ip, jp) = -1.4_dp/(lengths(ip)*lengths(jp))
328 modes = 0
329 ! G=0, both odd and even normal-only modes, and several modes with
330 ! appreciable exponential truncation reject the periodic K**2 formula.
331 modes(iz, 2:3) = [1, 2]
332 modes(ip, 4) = 1
333 modes(:, 5) = [1, 2, 1]
334 DO mode = 1, SIZE(modes, 2)
335 miller = modes(:, mode)
336 g = 2.0_dp*pi*matmul(transpose(h_inv), real(miller, dp))
337 CALL mt2d_kernel_strain(g, iz, lengths(iz), kernel, strain)
338 CALL check_error(kernel, mt2d_kernel_reference(g, iz, lengths(iz)), 3.e-14_dp)
339 CALL check_error(maxval(abs(strain(iz, :))), 0.0_dp, 0.0_dp)
340 CALL check_error(maxval(abs(strain(:, iz))), 0.0_dp, 0.0_dp)
341 IF (mode <= 3) CALL check_error(maxval(abs(strain)), 0.0_dp, 0.0_dp)
342 DO alpha = 1, 3
343 IF (alpha == iz) cycle
344 DO beta = 1, 3
345 IF (beta == iz) cycle
346 DO sign_step = 1, 4
347 ! g'=(I+epsilon_ab)^(-T) g, evaluated independently of dK.
348 shifted_g = g
349 IF (alpha == beta) THEN
350 shifted_g(beta) = g(beta)/(1.0_dp + shifts(sign_step))
351 ELSE
352 shifted_g(beta) = g(beta) - shifts(sign_step)*g(alpha)
353 END IF
354 shifted_kernel(sign_step) = mt2d_kernel_reference(shifted_g, iz, lengths(iz))
355 END DO
356 derivative = (8.0_dp*(shifted_kernel(1) - shifted_kernel(2)) &
357 - shifted_kernel(3) + shifted_kernel(4))/(12.0_dp*step)
358 CALL check_error(strain(alpha, beta), derivative, 3.e-9_dp)
359 END DO
360 END DO
361 END DO
362 END DO
363 END SUBROUTINE check_mt2d_kernel
364
365! **************************************************************************************************
366!> \brief Evaluate the original MT2D influence kernel independently of its strain derivative.
367!> \param g ...
368!> \param iz ...
369!> \param slab_size ...
370!> \return ...
371! **************************************************************************************************
372 FUNCTION mt2d_kernel_reference(g, iz, slab_size) RESULT(kernel)
373 REAL(dp), INTENT(IN) :: g(3)
374 INTEGER, INTENT(IN) :: iz
375 REAL(dp), INTENT(IN) :: slab_size
376 REAL(dp) :: kernel
377
378 REAL(dp) :: g2, g_parallel(3), radius
379
380 g2 = sum(g**2)
381 IF (g2 == 0.0_dp) THEN
382 kernel = pi*slab_size**2/2.0_dp
383 ELSE
384 g_parallel = g
385 g_parallel(iz) = 0.0_dp
386 radius = slab_size/2.0_dp
387 kernel = fourpi/g2 - fourpi/g2*cos(g(iz)*radius)*exp(-norm2(g_parallel)*radius)
388 END IF
389 END FUNCTION mt2d_kernel_reference
390
391! **************************************************************************************************
392!> \brief Require the scaled error to be finite and within the supplied tolerance.
393!> \param value ...
394!> \param reference ...
395!> \param tolerance ...
396! **************************************************************************************************
397 SUBROUTINE check_error(value, reference, tolerance)
398 REAL(dp), INTENT(IN) :: value, reference, tolerance
399
400 REAL(dp) :: error
401
402 error = abs(value - reference)/max(1.0_dp, abs(reference))
403 max_error = max(max_error, error)
404 IF (.NOT. (error <= tolerance)) THEN
405 print *, 'Coulomb check: value, reference, error, tolerance:', value, reference, error, tolerance
406 error stop 'PW integral or MT2D kernel check failed'
407 END IF
408 END SUBROUTINE check_error
409
410END PROGRAM pw_unittest
various routines to log and control the output. The idea is that decisions about where to log should ...
type(cp_logger_type) function, pointer, public cp_get_default_logger()
returns the default logger
Defines the basic variable types.
Definition kinds.F:23
integer, parameter, public dp
Definition kinds.F:34
CP2K C/C++ interface.
Definition libcp2k.F:20
subroutine, public cp2k_finalize()
...
Definition libcp2k.F:139
subroutine, public cp2k_init()
...
Definition libcp2k.F:104
Definition of mathematical constants and functions.
real(kind=dp), parameter, public pi
real(kind=dp), parameter, public fourpi
Interface to the message passing library MPI.
type(mp_comm_type), parameter, public mp_comm_world
subroutine, public mt2d_kernel_strain(g, special_dimension, slab_size, kernel, strain)
MT2D Coulomb kernel and its affine strain derivative in the periodic plane.
Definition mt_util.F:49
subroutine, public pw_copy_match(pw1, pw2)
copy a pw type variable
Definition pw_copy_all.F:43
integer, parameter, public halfspace
integer, parameter, public fullspace
This module defines the grid data type and some basic operations on it.
Definition pw_grids.F:36
subroutine, public pw_grid_release(pw_grid)
releases the given pw grid
Definition pw_grids.F:2201
integer, parameter, public do_accurate_sum
Definition pw_methods.F:80
integer, parameter, public do_standard_sum
Definition pw_methods.F:80
subroutine, public pw_poisson_set(poisson_env, cell_hmat, parameters, pw_pools, use_level, mt_super_ref_pw_grid, dct_pw_grid, force_rebuild)
sets cell, grids and parameters used by the poisson solver You should call this at least once (and se...
functions related to the poisson solver on regular grids
integer, parameter, public pw_poisson_mt
Manages a pool of grids (to be used for example as tmp objects), but can also be used to instantiate ...
subroutine, public pw_pool_create(pool, pw_grid, max_cache)
creates a pool for pw
Generate Gaussian cube files.
character(len= *), parameter, public cube_values_format
character(len= *), parameter, public cube_value_format
subroutine, public cube_read_values(values, buffer)
Read cube values from a character buffer.
subroutine check_cube_values()
Preserve three-digit cube exponents and parse adjacent values across z-slices.
Definition pw_unittest.F:66
program pw_unittest
Definition pw_unittest.F:8
type of a logger, at the moment it contains just a print level starting at which level it should be l...
stores all the informations relevant to an mpi environment
environment for the poisson solver