Octopus
test_poisson_fft_batch.F90
Go to the documentation of this file.
1!! Copyright (C) 2026 A. Buccheri, H. Menke
2!!
3!! This program is free software; you can redistribute it and/or modify
4!! it under the terms of the GNU General Public License as published by
5!! the Free Software Foundation; either version 2, or (at your option)
6!! any later version.
7!!
8!! This program is distributed in the hope that it will be useful,
9!! but WITHOUT ANY WARRANTY; without even the implied warranty of
10!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11!! GNU General Public License for more details.
12!!
13!! You should have received a copy of the GNU General Public License
14!! along with this program; if not, write to the Free Software
15!! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16!! 02110-1301, USA.
17
18#include "global.h"
19
21 use, intrinsic :: iso_fortran_env
22
24 use debug_oct_m
28 use global_oct_m
30 use math_oct_m
32 use mesh_oct_m, only: mesh_t
33 use mpi_oct_m, only: mpi_world
38 use xc_cam_oct_m, only: cam_null
39
40 implicit none
41 private
43
44contains
45
51 subroutine mock_plane_wave_state(mesh, g_vector, state)
52 class(mesh_t), intent(in) :: mesh
53 real(real64), intent(in) :: g_vector(:)
54 complex(real64), intent(out) :: state(:)
55
56 complex(real64), parameter :: two_pi_i = m_two * m_pi * m_zi
57 integer :: ip
58
59 assert(size(g_vector) == size(mesh%chi, 1))
60 assert(size(state) == mesh%np)
61
62 !$omp parallel do default(shared)
63 do ip = 1, mesh%np
64 state(ip) = exp(two_pi_i * dot_product(g_vector, mesh%chi(:, ip)))
65 enddo
66 !$omp end parallel do
67
68 end subroutine mock_plane_wave_state
69
70
71 subroutine test_poisson_fft_batch(namespace)
72 type(namespace_t), intent(in) :: namespace
73
74 integer, parameter :: batch_size = 4
76 integer, parameter :: count_partial = 3
77#ifndef NDEBUG
78 real(real64), parameter :: tolerance = 1.0e-10_real64
79#endif
80 real(real64), parameter :: reinit_q_value = 1e9_real64
81
82 type(electrons_t), pointer :: sys
83 type(poisson_t) :: psolver_single, psolver_batch, psolver_batch_last
84 type(poisson_t) :: psolver_real_batch, psolver_real_single
85 type(fourier_space_op_t) :: coulb
86 complex(real64), allocatable :: rho_batch(:, :), pot(:), pot_batch(:, :), pot_partial(:, :), pot_batch_last(:, :)
87 complex(real64), allocatable :: pot_real_batch(:, :), pot_real(:)
88 real(real64), allocatable :: diff(:), max_abs_diff(:), mean_diff(:), max_abs_diff_partial(:), max_abs_diff_last(:)
89 real(real64), allocatable :: max_abs_diff_real(:)
90 integer :: ipair
91 real(real64) :: qred(3)
92
94 real(real64), parameter :: dG(3, batch_size) = reshape([ &
95 1, 0, 0, &
96 0, 1, 0, &
97 1, 1, 0, &
98 -1, 2, 1 &
99 ], [3, batch_size])
100
101 push_sub(test_poisson_fft_batch)
102
103 call messages_write('Info: Testing batched FFT Poisson solve for periodic k-point pair products')
104 call messages_info(namespace=namespace)
105
106 ! Non-Gamma q-point in reduced coordinates.
107 qred = [0.25_real64, 0.125_real64, 0.0_real64]
108
109 ! Initialise the system; only the grid (sys%gr) is used below, as the codensities are mocked
110 call calc_mode_par%set_parallelization(p_strategy_states, default=.false.)
111 sys => electrons_t(namespace, mpi_world, calc_mode_id=1)
112
113 ! Mocked densities
114 safe_allocate(rho_batch(1:sys%gr%np, 1:batch_size))
115 do ipair = 1, batch_size
116 call mock_plane_wave_state(sys%gr, dg(:, ipair), rho_batch(:, ipair))
117 enddo
118
119 ! -----------------------
120 ! Batch Poisson solve
121 ! -----------------------
122 call poisson_init(psolver_batch, namespace, sys%space, sys%gr%der, sys%mc, sys%gr%stencil, &
123 sys%st%qtot, label='Pair-product Poisson batch size 4', solver=poisson_fft, verbose=.false., &
124 force_serial=.true., force_cmplx=.true., fft_batch_size=batch_size)
125
126 ! Build the kernel
127 ! Initialisation done internally, triggered by change in q
128 safe_allocate(coulb%qq(1:sys%space%dim))
129 coulb%qq = reinit_q_value
130 call poisson_build_kernel(psolver_batch, namespace, sys%space, coulb, qred, cam_null)
131
132 safe_allocate(pot_batch(1:sys%gr%np, 1:batch_size))
133 call zpoisson_solve_batch(psolver_batch, pot_batch, rho_batch, view=fft_batch_unpacked, kernel=coulb)
134
135 ! Partial batch: solve only count_partial < batch_size columns with the same (capacity batch_size) solver.
136 ! This exercises the active-count path: the unused capacity lanes must not affect the result.
137 safe_allocate(pot_partial(1:sys%gr%np, 1:count_partial))
138 call zpoisson_solve_batch(psolver_batch, pot_partial, rho_batch(:, 1:count_partial), &
139 view=fft_batch_unpacked, kernel=coulb)
140 call poisson_end(psolver_batch)
141
142 ! -----------------------
143 ! Batch-last (batch_axis = 4) solve: exercises the cube batch-last layout (CPU FFTW only).
144 ! The same per-G Coulomb kernel applies (it is independent of the batch layout).
145 ! -----------------------
146 call poisson_init(psolver_batch_last, namespace, sys%space, sys%gr%der, sys%mc, sys%gr%stencil, &
147 sys%st%qtot, label='Pair-product Poisson batch size 4 (batch-last)', solver=poisson_fft, verbose=.false., &
148 force_serial=.true., force_cmplx=.true., fft_batch_size=batch_size, fft_batch_axis=4)
149 safe_allocate(pot_batch_last(1:sys%gr%np, 1:batch_size))
150 call zpoisson_solve_batch(psolver_batch_last, pot_batch_last, rho_batch, view=fft_batch_unpacked, kernel=coulb)
151 call poisson_end(psolver_batch_last)
152
153 ! -----------------------
154 ! Complex data on a REAL cube (force_cmplx = .false.): the batched solver cannot perform a
155 ! complex-to-complex FFT on a real cube, so it must fall back to transforming the real and
156 ! imaginary parts separately with the real FFT, mirroring the scalar zpoisson_solve fallback.
157 ! This is the layout the time-dependent exact-exchange / OEP-KLI path hits: the cube is sized
158 ! for the real ground state, while the TD codensities are complex. The real/imag split requires
159 ! a Gamma (q = 0) kernel, so no external q-shifted kernel is used here; the batched solve is
160 ! checked against the per-column scalar solve on the same real cube.
161 ! -----------------------
162 call poisson_init(psolver_real_batch, namespace, sys%space, sys%gr%der, sys%mc, sys%gr%stencil, &
163 sys%st%qtot, label='Pair-product Poisson real cube batch size 4', solver=poisson_fft, &
164 verbose=.false., force_serial=.true., force_cmplx=.false., fft_batch_size=batch_size)
165 safe_allocate(pot_real_batch(1:sys%gr%np, 1:batch_size))
166 call zpoisson_solve_batch(psolver_real_batch, pot_real_batch, rho_batch, view=fft_batch_unpacked)
167 call poisson_end(psolver_real_batch)
168
169 write(message(1), '(a)') 'Test Poisson FFT batching: Performed batch solve'
170 call messages_info(1, namespace=namespace)
171
172 ! -----------------------
173 ! Single Poisson solves
174 ! -----------------------
175 call poisson_init(psolver_single, namespace, sys%space, sys%gr%der, sys%mc, sys%gr%stencil, &
176 sys%st%qtot, label='Pair-product Poisson batch size 1', solver=poisson_fft, verbose=.false., &
177 force_serial=.true., force_cmplx=.true., fft_batch_size=1)
178
179 ! Single-solve reference on a real cube, to validate the complex-on-real-cube batched solve above.
180 call poisson_init(psolver_real_single, namespace, sys%space, sys%gr%der, sys%mc, sys%gr%stencil, &
181 sys%st%qtot, label='Pair-product Poisson real cube batch size 1', solver=poisson_fft, &
182 verbose=.false., force_serial=.true., force_cmplx=.false., fft_batch_size=1)
183
184 safe_allocate(pot(1:sys%gr%np))
185 safe_allocate(pot_real(1:sys%gr%np))
186 safe_allocate(diff(1:sys%gr%np))
187 safe_allocate(mean_diff(1:batch_size))
188 safe_allocate(max_abs_diff(1:batch_size))
189 safe_allocate(max_abs_diff_partial(1:count_partial))
190 safe_allocate(max_abs_diff_last(1:batch_size))
191 safe_allocate(max_abs_diff_real(1:batch_size))
192
193 do ipair = 1, batch_size
194 ! Reuse the same kernel
195 call zpoisson_solve(psolver_single, namespace, pot, rho_batch(:, ipair), all_nodes=.false., kernel=coulb)
196 diff = abs(pot_batch(:, ipair) - pot(:))
197 mean_diff(ipair) = sum(diff)/real(size(diff), real64)
198 max_abs_diff(ipair) = maxval(diff)
199 ! The batch-last solve must also match the single solve, lane by lane
200 max_abs_diff_last(ipair) = maxval(abs(pot_batch_last(:, ipair) - pot(:)))
201 ! The first count_partial columns must also match the partial (count < capacity) batch solve
202 if (ipair <= count_partial) then
203 max_abs_diff_partial(ipair) = maxval(abs(pot_partial(:, ipair) - pot(:)))
204 end if
205 ! The complex-on-real-cube batched solve must match the per-column scalar solve on the real
206 ! cube (both go through the real/imag split). No external kernel here (Gamma point).
207 call zpoisson_solve(psolver_real_single, namespace, pot_real, rho_batch(:, ipair), all_nodes=.false.)
208 max_abs_diff_real(ipair) = maxval(abs(pot_real_batch(:, ipair) - pot_real(:)))
209 end do
210
211 call poisson_end(psolver_single)
212 call poisson_end(psolver_real_single)
213 call fourier_space_op_end(coulb)
214
215 safe_deallocate_a(rho_batch)
216 safe_deallocate_a(pot_batch)
217 safe_deallocate_a(pot_batch_last)
218 safe_deallocate_a(pot_real_batch)
219 safe_deallocate_a(pot_partial)
220 safe_deallocate_a(pot)
221 safe_deallocate_a(pot_real)
222 safe_deallocate_a(diff)
223
224 write(message(1), '(a)') 'Test Poisson FFT batching: Performed a set of single solves'
225 call messages_info(1, namespace=namespace)
226
227 ! -----------------------
228 ! Error reporting
229 ! -----------------------
230 do ipair = 1, batch_size
231 write(message(1), '(a,i0,a,es18.10,a,es18.10)') 'Poisson FFT pair ', ipair, &
232 ': mean abs diff = ', mean_diff(ipair), ', max abs diff = ', max_abs_diff(ipair)
233 call messages_info(1, namespace=namespace)
234 end do
235 write(message(1), '(a,es18.10)') 'Poisson FFT batch overall mean abs diff = ', sum(mean_diff)/real(batch_size, real64)
236 write(message(2), '(a,es18.10)') 'Poisson FFT batch overall max abs diff = ', maxval(max_abs_diff)
237 call messages_info(2, namespace=namespace)
238
239 assert(all(max_abs_diff < tolerance))
240
241 write(message(1), '(a,es18.10)') 'Poisson FFT batch-last (batch_axis=4) overall max abs diff = ', &
242 maxval(max_abs_diff_last)
243 call messages_info(1, namespace=namespace)
244
245 assert(all(max_abs_diff_last < tolerance))
246
247 write(message(1), '(a,i0,a,i0,a,es18.10)') 'Poisson FFT partial batch (count ', count_partial, &
248 ' of capacity ', batch_size, ') overall max abs diff = ', maxval(max_abs_diff_partial)
249 call messages_info(1, namespace=namespace)
250
251 assert(all(max_abs_diff_partial < tolerance))
252
253 write(message(1), '(a,es18.10)') 'Poisson FFT complex-on-real-cube batch overall max abs diff = ', &
254 maxval(max_abs_diff_real)
255 call messages_info(1, namespace=namespace)
256
257 assert(all(max_abs_diff_real < tolerance))
258
259 call messages_write('Poisson FFT batch application test passed')
260 call messages_info(namespace=namespace)
261
262 safe_deallocate_a(mean_diff)
263 safe_deallocate_a(max_abs_diff)
264 safe_deallocate_a(max_abs_diff_partial)
265 safe_deallocate_a(max_abs_diff_last)
266 safe_deallocate_a(max_abs_diff_real)
267
268 call states_elec_deallocate_wfns(sys%st)
269 safe_deallocate_p(sys)
270
272
273 end subroutine test_poisson_fft_batch
274
276
277!! Local Variables:
278!! mode: f90
279!! coding: utf-8
280!! End:
double exp(double __x) __attribute__((__nothrow__
This module handles the calculation mode.
type(calc_mode_par_t), public calc_mode_par
Singleton instance of parallel calculation mode.
integer, parameter, public p_strategy_states
parallelization in states
Fast Fourier Transform module. This module provides a single interface that works with different FFT ...
Definition: fft.F90:120
integer, parameter, public fft_batch_unpacked
Definition: fft.F90:188
integer, parameter, public fft_batch_packed
Definition: fft.F90:188
subroutine, public fourier_space_op_end(this)
real(real64), parameter, public m_two
Definition: global.F90:202
real(real64), parameter, public m_pi
some mathematical constants
Definition: global.F90:198
complex(real64), parameter, public m_zi
Definition: global.F90:214
logical pure function, public kpoints_point_is_gamma(this, ik)
Definition: kpoints.F90:1720
This module is intended to contain "only mathematical" functions and procedures.
Definition: math.F90:117
This module defines the meshes, which are used in Octopus.
Definition: mesh.F90:120
character(len=256), dimension(max_lines), public message
to be output by fatal, warning
Definition: messages.F90:162
subroutine, public messages_info(no_lines, iunit, debug_only, stress, all_nodes, namespace)
Definition: messages.F90:594
type(mpi_grp_t), public mpi_world
Definition: mpi.F90:272
integer, parameter, public poisson_fft
Definition: poisson.F90:191
subroutine, public poisson_init(this, namespace, space, der, mc, stencil, qtot, label, solver, verbose, force_serial, force_cmplx, fft_batch_size, fft_batch_axis)
Definition: poisson.F90:236
subroutine, public zpoisson_solve(this, namespace, pot, rho, all_nodes, kernel, reset)
Definition: poisson.F90:845
subroutine, public zpoisson_solve_batch(this, pot, rho, kernel, view)
Solves the Poisson equation for batched quantities, using fast Fourier transforms (FFTs).
Definition: poisson.F90:2458
subroutine, public poisson_build_kernel(this, namespace, space, coulb, qq, cam, singul)
Definition: poisson.F90:1204
subroutine, public poisson_end(this)
Definition: poisson.F90:695
subroutine, public states_elec_deallocate_wfns(st)
Deallocates the KS wavefunctions defined within a states_elec_t structure.
subroutine mock_plane_wave_state(mesh, g_vector, state)
Construct a periodic plane-wave factor on the mesh.
subroutine, public test_poisson_fft_batch(namespace)
type(xc_cam_t), parameter, public cam_null
All CAM parameters set to zero.
Definition: xc_cam.F90:152
Class describing the electron system.
Definition: electrons.F90:222
Describes mesh distribution to nodes.
Definition: mesh.F90:187
int true(void)