Neko 1.99.5
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_neighbour.f90
Go to the documentation of this file.
1! Copyright (c) 2026, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
35 use num_types, only : rp
36 use gs_comm, only : gs_comm_t, gs_vec_nc
38 use stack, only : stack_i4_t
39 use mpi_f08, only : mpi_comm, mpi_request, mpi_status_ignore, &
40 mpi_info_null, mpi_dist_graph_create_adjacent, &
41 mpi_ineighbor_alltoallv, mpi_wait, mpi_comm_free
43 use, intrinsic :: iso_c_binding
44 use utils, only : neko_error
45 implicit none
46 private
47
56 type, public, extends(gs_comm_t) :: gs_neighbour_t
58 real(kind=rp), allocatable :: send_buf(:)
60 real(kind=rp), allocatable :: recv_buf(:)
64 integer, allocatable :: sendcounts(:), sdispls(:)
65 integer, allocatable :: recvcounts(:), rdispls(:)
67 type(mpi_comm) :: neigh_comm
69 type(mpi_request) :: request
74 real(kind=rp), allocatable :: send_buf_v(:)
75 real(kind=rp), allocatable :: recv_buf_v(:)
76 integer, allocatable :: sendcounts_v(:), sdispls_v(:)
77 integer, allocatable :: recvcounts_v(:), rdispls_v(:)
78 contains
79 procedure, pass(this) :: init => gs_neighbour_init
80 procedure, pass(this) :: free => gs_neighbour_free
82 procedure, pass(this) :: nbsend => gs_nbsend_neighbour
83 procedure, pass(this) :: nbrecv => gs_nbrecv_neighbour
84 procedure, pass(this) :: nbwait => gs_nbwait_neighbour
85 procedure, pass(this) :: nbsend_vec => gs_nbsend_vec_neighbour
86 procedure, pass(this) :: nbrecv_vec => gs_nbrecv_vec_neighbour
87 procedure, pass(this) :: nbwait_vec => gs_nbwait_vec_neighbour
88 end type gs_neighbour_t
89
90contains
91
94 subroutine gs_neighbour_init(this, send_pe, recv_pe)
95 class(gs_neighbour_t), intent(inout) :: this
96 type(stack_i4_t), intent(inout) :: send_pe
97 type(stack_i4_t), intent(inout) :: recv_pe
98 integer :: i, nsend, nrecv, send_total, recv_total, ierr
102 integer, allocatable :: src_weights(:), dst_weights(:)
103
104 call this%init_order(send_pe, recv_pe)
105
106 nsend = size(this%send_pe)
107 nrecv = size(this%recv_pe)
108
109 ! Allocate the count/displacement arrays with at least one element so
110 ! that a peerless rank (nsend or nrecv == 0) still passes a valid array
111 ! to the collective.
112 allocate(this%sendcounts(max(1, nsend)), this%sdispls(max(1, nsend)))
113 allocate(this%recvcounts(max(1, nrecv)), this%rdispls(max(1, nrecv)))
114 this%sendcounts = 0
115 this%sdispls = 0
116 this%recvcounts = 0
117 this%rdispls = 0
118
119 send_total = 0
120 do i = 1, nsend
121 this%sendcounts(i) = this%send_dof(this%send_pe(i))%size()
122 this%sdispls(i) = send_total
123 send_total = send_total + this%sendcounts(i)
124 end do
125 allocate(this%send_buf(max(1, send_total)))
126
127 recv_total = 0
128 do i = 1, nrecv
129 this%recvcounts(i) = this%recv_dof(this%recv_pe(i))%size()
130 this%rdispls(i) = recv_total
131 recv_total = recv_total + this%recvcounts(i)
132 end do
133 allocate(this%recv_buf(max(1, recv_total)))
134
135 ! Fused vector exchange buffers/descriptors, sized for GS_VEC_NC comps.
136 allocate(this%send_buf_v(max(1, gs_vec_nc*send_total)))
137 allocate(this%recv_buf_v(max(1, gs_vec_nc*recv_total)))
138 allocate(this%sendcounts_v(max(1, nsend)), this%sdispls_v(max(1, nsend)))
139 allocate(this%recvcounts_v(max(1, nrecv)), this%rdispls_v(max(1, nrecv)))
140 this%sendcounts_v = 0
141 this%sdispls_v = 0
142 this%recvcounts_v = 0
143 this%rdispls_v = 0
144 this%vec_supported = .true.
145
146 ! Build a distributed-graph communicator over the halo neighbourhood.
147 ! With MPI_Dist_graph_create_adjacent and reorder = .false. the order of
148 ! sources/destinations is preserved, so the j-th block of the collective
149 ! corresponds to recv_pe(j)/send_pe(j) and the count/displacement arrays
150 ! above can be used directly.
151 allocate(src_weights(max(1, nrecv)), dst_weights(max(1, nsend)))
152 src_weights = 1
153 dst_weights = 1
154 call mpi_dist_graph_create_adjacent(neko_comm, &
155 nrecv, this%recv_pe, src_weights, &
156 nsend, this%send_pe, dst_weights, &
157 mpi_info_null, .false., this%neigh_comm, ierr)
158 deallocate(src_weights, dst_weights)
159
160 end subroutine gs_neighbour_init
161
163 subroutine gs_neighbour_free(this)
164 class(gs_neighbour_t), intent(inout) :: this
165 integer :: ierr
166
167 if (allocated(this%send_buf)) deallocate(this%send_buf)
168 if (allocated(this%recv_buf)) deallocate(this%recv_buf)
169 if (allocated(this%sendcounts)) deallocate(this%sendcounts)
170 if (allocated(this%sdispls)) deallocate(this%sdispls)
171 if (allocated(this%recvcounts)) deallocate(this%recvcounts)
172 if (allocated(this%rdispls)) deallocate(this%rdispls)
173
174 if (allocated(this%send_buf_v)) deallocate(this%send_buf_v)
175 if (allocated(this%recv_buf_v)) deallocate(this%recv_buf_v)
176 if (allocated(this%sendcounts_v)) deallocate(this%sendcounts_v)
177 if (allocated(this%sdispls_v)) deallocate(this%sdispls_v)
178 if (allocated(this%recvcounts_v)) deallocate(this%recvcounts_v)
179 if (allocated(this%rdispls_v)) deallocate(this%rdispls_v)
180
181 call mpi_comm_free(this%neigh_comm, ierr)
182
183 call this%free_order()
184 call this%free_dofs()
185
186 end subroutine gs_neighbour_free
187
191 subroutine gs_nbsend_neighbour(this, u, n, tag, deps, strm)
192 class(gs_neighbour_t), intent(inout) :: this
193 integer, intent(in) :: n
194 real(kind=rp), dimension(n), intent(inout) :: u
195 integer, intent(in) :: tag
196 type(c_ptr), intent(inout) :: deps
197 type(c_ptr), intent(inout) :: strm
198 integer :: i, j, dst, off, ndst, ierr
199
200 ! Gather data from u into the per-peer slab of send_buf according to the
201 ! indices in send_dof. Each thread packs a different peer's slab; the
202 ! implicit barrier at end-do guarantees the buffer is complete before
203 ! the master fires the collective.
204 !$omp do
205 do i = 1, size(this%send_pe)
206 dst = this%send_pe(i)
207 off = this%sdispls(i)
208 ndst = this%sendcounts(i)
209 select type (dofs => this%send_dof(dst)%data)
210 type is (integer)
211 !$omp simd
212 do j = 1, ndst
213 this%send_buf(off + j) = u(dofs(j))
214 end do
215 end select
216 end do
217 !$omp end do
218
219 ! A neighbourhood collective is one call over neigh_comm and must be
220 ! issued by a single thread.
221 !$omp master
222 call mpi_ineighbor_alltoallv(this%send_buf, this%sendcounts, &
223 this%sdispls, mpi_real_precision, this%recv_buf, this%recvcounts, &
224 this%rdispls, mpi_real_precision, this%neigh_comm, this%request, &
225 ierr)
226 !$omp end master
227
228 end subroutine gs_nbsend_neighbour
229
232 subroutine gs_nbrecv_neighbour(this, tag)
233 class(gs_neighbour_t), intent(inout) :: this
234 integer, intent(in) :: tag
235 ! Nothing to do: the collective is launched in nbsend.
236 end subroutine gs_nbrecv_neighbour
237
239 subroutine gs_nbwait_neighbour(this, u, n, op, strm)
240 class(gs_neighbour_t), intent(inout) :: this
241 integer, intent(in) :: n
242 real(kind=rp), dimension(n), intent(inout) :: u
243 type(c_ptr), intent(inout) :: strm
244 integer :: i, j, src, off, nsrc
245 integer :: op
246 integer :: ierr
247
248 ! The collective is master-issued, so wait on it from the master and
249 ! barrier before anyone touches recv_buf.
250 !$omp master
251 call mpi_wait(this%request, mpi_status_ignore, ierr)
252 !$omp end master
253 !$omp barrier
254
255 ! Reduce each received slab into u. The outer loop over peers stays
256 ! serial: a dof shared by 3+ ranks appears in several recv_dof lists, so
257 ! reducing two slabs concurrently would race on that dof. Parallelism is
258 ! taken within each slab instead.
259 do i = 1, size(this%recv_pe)
260 src = this%recv_pe(i)
261 off = this%rdispls(i)
262 nsrc = this%recvcounts(i)
263 select type (dofs => this%recv_dof(src)%data)
264 type is (integer)
265 select case (op)
266 case (gs_op_add)
267 !OCL NORECURRENCE, NOVREC, NOALIAS
268 !DIR$ CONCURRENT
269 !GCC$ ivdep
270 !NEC$ IVDEP
271 !$omp do
272 do j = 1, nsrc
273 u(dofs(j)) = u(dofs(j)) + this%recv_buf(off + j)
274 end do
275 !$omp end do
276 case (gs_op_mul)
277 !OCL NORECURRENCE, NOVREC, NOALIAS
278 !DIR$ CONCURRENT
279 !GCC$ ivdep
280 !NEC$ IVDEP
281 !$omp do
282 do j = 1, nsrc
283 u(dofs(j)) = u(dofs(j)) * this%recv_buf(off + j)
284 end do
285 !$omp end do
286 case (gs_op_min)
287 !OCL NORECURRENCE, NOVREC, NOALIAS
288 !DIR$ CONCURRENT
289 !GCC$ ivdep
290 !NEC$ IVDEP
291 !$omp do
292 do j = 1, nsrc
293 u(dofs(j)) = min(u(dofs(j)), this%recv_buf(off + j))
294 end do
295 !$omp end do
296 case (gs_op_max)
297 !OCL NORECURRENCE, NOVREC, NOALIAS
298 !DIR$ CONCURRENT
299 !GCC$ ivdep
300 !NEC$ IVDEP
301 !$omp do
302 do j = 1, nsrc
303 u(dofs(j)) = max(u(dofs(j)), this%recv_buf(off + j))
304 end do
305 !$omp end do
306 case default
307 call neko_error("Unknown operation in gs_nbwait_neighbour")
308 end select
309 end select
310 end do
311
312 end subroutine gs_nbwait_neighbour
313
318 subroutine gs_nbsend_vec_neighbour(this, u, n, nc, tag, deps, strm)
319 class(gs_neighbour_t), intent(inout) :: this
320 integer, intent(in) :: n, nc
321 real(kind=rp), dimension(nc*n), intent(inout) :: u
322 integer, intent(in) :: tag
323 type(c_ptr), intent(inout) :: deps
324 type(c_ptr), intent(inout) :: strm
325 integer :: i, j, c, dst, off, ndst, ierr
326
327 !$omp do
328 do i = 1, size(this%send_pe)
329 dst = this%send_pe(i)
330 off = this%sdispls(i)
331 ndst = this%sendcounts(i)
332 select type (dofs => this%send_dof(dst)%data)
333 type is (integer)
334 do c = 1, nc
335 !$omp simd
336 do j = 1, ndst
337 this%send_buf_v(nc*off + (c-1)*ndst + j) = u((c-1)*n + dofs(j))
338 end do
339 end do
340 end select
341 end do
342 !$omp end do
343
344 ! A neighbourhood collective is one call over neigh_comm and must be
345 ! issued by a single thread. The end-do barrier above guarantees the
346 ! send buffer is fully packed first.
347 !$omp master
348 do i = 1, size(this%send_pe)
349 this%sendcounts_v(i) = nc * this%sendcounts(i)
350 this%sdispls_v(i) = nc * this%sdispls(i)
351 end do
352 do i = 1, size(this%recv_pe)
353 this%recvcounts_v(i) = nc * this%recvcounts(i)
354 this%rdispls_v(i) = nc * this%rdispls(i)
355 end do
356 call mpi_ineighbor_alltoallv(this%send_buf_v, this%sendcounts_v, &
357 this%sdispls_v, mpi_real_precision, this%recv_buf_v, &
358 this%recvcounts_v, this%rdispls_v, mpi_real_precision, &
359 this%neigh_comm, this%request, ierr)
360 !$omp end master
361
362 end subroutine gs_nbsend_vec_neighbour
363
365 subroutine gs_nbrecv_vec_neighbour(this, tag, nc)
366 class(gs_neighbour_t), intent(inout) :: this
367 integer, intent(in) :: tag, nc
368 ! Nothing to do: the collective is launched in nbsend_vec.
369 end subroutine gs_nbrecv_vec_neighbour
370
374 subroutine gs_nbwait_vec_neighbour(this, u, n, nc, op, strm)
375 class(gs_neighbour_t), intent(inout) :: this
376 integer, intent(in) :: n, nc
377 real(kind=rp), dimension(nc*n), intent(inout) :: u
378 type(c_ptr), intent(inout) :: strm
379 integer :: i, j, c, src, off, nsrc, ierr
380 integer :: op
381
382 !$omp master
383 call mpi_wait(this%request, mpi_status_ignore, ierr)
384 !$omp end master
385 !$omp barrier
386
387 ! Serial over peers (a dof shared by 3+ ranks appears in several recv
388 ! lists); parallelism is taken within each slab.
389 do i = 1, size(this%recv_pe)
390 src = this%recv_pe(i)
391 off = this%rdispls(i)
392 nsrc = this%recvcounts(i)
393 select type (dofs => this%recv_dof(src)%data)
394 type is (integer)
395 select case (op)
396 case (gs_op_add)
397 !$omp do
398 do j = 1, nsrc
399 do c = 1, nc
400 u((c-1)*n + dofs(j)) = u((c-1)*n + dofs(j)) + &
401 this%recv_buf_v(nc*off + (c-1)*nsrc + j)
402 end do
403 end do
404 !$omp end do
405 case (gs_op_mul)
406 !$omp do
407 do j = 1, nsrc
408 do c = 1, nc
409 u((c-1)*n + dofs(j)) = u((c-1)*n + dofs(j)) * &
410 this%recv_buf_v(nc*off + (c-1)*nsrc + j)
411 end do
412 end do
413 !$omp end do
414 case (gs_op_min)
415 !$omp do
416 do j = 1, nsrc
417 do c = 1, nc
418 u((c-1)*n + dofs(j)) = min(u((c-1)*n + dofs(j)), &
419 this%recv_buf_v(nc*off + (c-1)*nsrc + j))
420 end do
421 end do
422 !$omp end do
423 case (gs_op_max)
424 !$omp do
425 do j = 1, nsrc
426 do c = 1, nc
427 u((c-1)*n + dofs(j)) = max(u((c-1)*n + dofs(j)), &
428 this%recv_buf_v(nc*off + (c-1)*nsrc + j))
429 end do
430 end do
431 !$omp end do
432 case default
433 call neko_error("Unknown operation in gs_nbwait_vec_neighbour")
434 end select
435 end select
436 end do
437
438 end subroutine gs_nbwait_vec_neighbour
439
440end module gs_neighbour
Definition comm.F90:1
type(mpi_datatype), public mpi_real_precision
MPI type for working precision of REAL types.
Definition comm.F90:53
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:45
Defines a gather-scatter communication method.
Definition gs_comm.f90:34
integer, parameter, public gs_vec_nc
Maximum number of components handled by the fused vector (multi-component) halo exchange used by gs_o...
Definition gs_comm.f90:49
Defines gather-scatter communication using MPI neighbourhood collectives.
subroutine gs_nbwait_neighbour(this, u, n, op, strm)
Wait for the neighbourhood collective and reduce the received slabs.
subroutine gs_nbsend_vec_neighbour(this, u, n, nc, tag, deps, strm)
Pack the nc components and launch a single neighbourhood collective.
subroutine gs_nbwait_vec_neighbour(this, u, n, nc, op, strm)
Wait for the vector collective and reduce each received slab into u. Peer i's received slab holds nc ...
subroutine gs_nbrecv_neighbour(this, tag)
No-op: the neighbourhood collective issued in nbsend handles both the send and receive directions,...
subroutine gs_neighbour_free(this)
Deallocate the neighbourhood-collective communication method.
subroutine gs_neighbour_init(this, send_pe, recv_pe)
Initialise the neighbourhood-collective communication method See gs_comm.f90 for details.
subroutine gs_nbsend_neighbour(this, u, n, tag, deps, strm)
Pack the send buffer and initiate the neighbourhood collective. The collective covers both directions...
subroutine gs_nbrecv_vec_neighbour(this, tag, nc)
No-op: the collective is launched in nbsend_vec.
Defines Gather-scatter operations.
Definition gs_ops.f90:34
integer, parameter, public gs_op_add
Definition gs_ops.f90:36
integer, parameter, public gs_op_max
Definition gs_ops.f90:36
integer, parameter, public gs_op_min
Definition gs_ops.f90:36
integer, parameter, public gs_op_mul
Definition gs_ops.f90:36
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:12
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:52
Gather-scatter communication using an MPI neighbourhood collective. The whole halo exchange is carrie...
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40