Neko 1.99.9
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) :: init_vec => gs_neighbour_init_vec
86 procedure, pass(this) :: nbsend_vec => gs_nbsend_vec_neighbour
87 procedure, pass(this) :: nbrecv_vec => gs_nbrecv_vec_neighbour
88 procedure, pass(this) :: nbwait_vec => gs_nbwait_vec_neighbour
89 end type gs_neighbour_t
90
91contains
92
95 subroutine gs_neighbour_init(this, send_pe, recv_pe)
96 class(gs_neighbour_t), intent(inout) :: this
97 type(stack_i4_t), intent(inout) :: send_pe
98 type(stack_i4_t), intent(inout) :: recv_pe
99 integer :: i, nsend, nrecv, send_total, recv_total, ierr
103 integer, allocatable :: src_weights(:), dst_weights(:)
104
105 call this%init_order(send_pe, recv_pe)
106
107 nsend = size(this%send_pe)
108 nrecv = size(this%recv_pe)
109
110 ! Allocate the count/displacement arrays with at least one element so
111 ! that a peerless rank (nsend or nrecv == 0) still passes a valid array
112 ! to the collective.
113 allocate(this%sendcounts(max(1, nsend)), this%sdispls(max(1, nsend)))
114 allocate(this%recvcounts(max(1, nrecv)), this%rdispls(max(1, nrecv)))
115 this%sendcounts = 0
116 this%sdispls = 0
117 this%recvcounts = 0
118 this%rdispls = 0
119
120 send_total = 0
121 do i = 1, nsend
122 this%sendcounts(i) = this%send_dof(this%send_pe(i))%size()
123 this%sdispls(i) = send_total
124 send_total = send_total + this%sendcounts(i)
125 end do
126 allocate(this%send_buf(max(1, send_total)))
127
128 recv_total = 0
129 do i = 1, nrecv
130 this%recvcounts(i) = this%recv_dof(this%recv_pe(i))%size()
131 this%rdispls(i) = recv_total
132 recv_total = recv_total + this%recvcounts(i)
133 end do
134 allocate(this%recv_buf(max(1, recv_total)))
135
136 this%vec_supported = .true.
137 this%vec_ready = .false.
138
139 ! Build a distributed-graph communicator over the halo neighbourhood.
140 ! With MPI_Dist_graph_create_adjacent and reorder = .false. the order of
141 ! sources/destinations is preserved, so the j-th block of the collective
142 ! corresponds to recv_pe(j)/send_pe(j) and the count/displacement arrays
143 ! above can be used directly.
144 allocate(src_weights(max(1, nrecv)), dst_weights(max(1, nsend)))
145 src_weights = 1
146 dst_weights = 1
147 call mpi_dist_graph_create_adjacent(neko_comm, &
148 nrecv, this%recv_pe, src_weights, &
149 nsend, this%send_pe, dst_weights, &
150 mpi_info_null, .false., this%neigh_comm, ierr)
151 deallocate(src_weights, dst_weights)
152
153 end subroutine gs_neighbour_init
154
160 subroutine gs_neighbour_init_vec(this)
161 class(gs_neighbour_t), intent(inout) :: this
162 integer :: nsend, nrecv, send_total, recv_total
163
164 nsend = size(this%send_pe)
165 nrecv = size(this%recv_pe)
166 send_total = sum(this%sendcounts)
167 recv_total = sum(this%recvcounts)
168
169 allocate(this%send_buf_v(max(1, gs_vec_nc*send_total)))
170 allocate(this%recv_buf_v(max(1, gs_vec_nc*recv_total)))
171 allocate(this%sendcounts_v(max(1, nsend)), this%sdispls_v(max(1, nsend)))
172 allocate(this%recvcounts_v(max(1, nrecv)), this%rdispls_v(max(1, nrecv)))
173 this%sendcounts_v = 0
174 this%sdispls_v = 0
175 this%recvcounts_v = 0
176 this%rdispls_v = 0
177
178 end subroutine gs_neighbour_init_vec
179
181 subroutine gs_neighbour_free(this)
182 class(gs_neighbour_t), intent(inout) :: this
183 integer :: ierr
184
185 if (allocated(this%send_buf)) deallocate(this%send_buf)
186 if (allocated(this%recv_buf)) deallocate(this%recv_buf)
187 if (allocated(this%sendcounts)) deallocate(this%sendcounts)
188 if (allocated(this%sdispls)) deallocate(this%sdispls)
189 if (allocated(this%recvcounts)) deallocate(this%recvcounts)
190 if (allocated(this%rdispls)) deallocate(this%rdispls)
191
192 if (allocated(this%send_buf_v)) deallocate(this%send_buf_v)
193 if (allocated(this%recv_buf_v)) deallocate(this%recv_buf_v)
194 if (allocated(this%sendcounts_v)) deallocate(this%sendcounts_v)
195 if (allocated(this%sdispls_v)) deallocate(this%sdispls_v)
196 if (allocated(this%recvcounts_v)) deallocate(this%recvcounts_v)
197 if (allocated(this%rdispls_v)) deallocate(this%rdispls_v)
198 this%vec_ready = .false.
199
200 call mpi_comm_free(this%neigh_comm, ierr)
201
202 call this%free_order()
203 call this%free_dofs()
204
205 end subroutine gs_neighbour_free
206
210 subroutine gs_nbsend_neighbour(this, u, n, tag, deps, strm)
211 class(gs_neighbour_t), intent(inout) :: this
212 integer, intent(in) :: n
213 real(kind=rp), dimension(n), intent(inout) :: u
214 integer, intent(in) :: tag
215 type(c_ptr), intent(inout) :: deps
216 type(c_ptr), intent(inout) :: strm
217 integer :: i, j, dst, off, ndst, ierr
218
219 ! Gather data from u into the per-peer slab of send_buf according to the
220 ! indices in send_dof. Each thread packs a different peer's slab; the
221 ! implicit barrier at end-do guarantees the buffer is complete before
222 ! the master fires the collective.
223 !$omp do
224 do i = 1, size(this%send_pe)
225 dst = this%send_pe(i)
226 off = this%sdispls(i)
227 ndst = this%sendcounts(i)
228 select type (dofs => this%send_dof(dst)%data)
229 type is (integer)
230 !$omp simd
231 do j = 1, ndst
232 this%send_buf(off + j) = u(dofs(j))
233 end do
234 end select
235 end do
236 !$omp end do
237
238 ! A neighbourhood collective is one call over neigh_comm and must be
239 ! issued by a single thread.
240 !$omp master
241 call mpi_ineighbor_alltoallv(this%send_buf, this%sendcounts, &
242 this%sdispls, mpi_real_precision, this%recv_buf, this%recvcounts, &
243 this%rdispls, mpi_real_precision, this%neigh_comm, this%request, &
244 ierr)
245 !$omp end master
246
247 end subroutine gs_nbsend_neighbour
248
251 subroutine gs_nbrecv_neighbour(this, tag)
252 class(gs_neighbour_t), intent(inout) :: this
253 integer, intent(in) :: tag
254 ! Nothing to do: the collective is launched in nbsend.
255 end subroutine gs_nbrecv_neighbour
256
258 subroutine gs_nbwait_neighbour(this, u, n, op, strm)
259 class(gs_neighbour_t), intent(inout) :: this
260 integer, intent(in) :: n
261 real(kind=rp), dimension(n), intent(inout) :: u
262 type(c_ptr), intent(inout) :: strm
263 integer :: i, j, src, off, nsrc
264 integer :: op
265 integer :: ierr
266
267 ! The collective is master-issued, so wait on it from the master and
268 ! barrier before anyone touches recv_buf.
269 !$omp master
270 call mpi_wait(this%request, mpi_status_ignore, ierr)
271 !$omp end master
272 !$omp barrier
273
274 ! Reduce each received slab into u. The outer loop over peers stays
275 ! serial: a dof shared by 3+ ranks appears in several recv_dof lists, so
276 ! reducing two slabs concurrently would race on that dof. Parallelism is
277 ! taken within each slab instead.
278 do i = 1, size(this%recv_pe)
279 src = this%recv_pe(i)
280 off = this%rdispls(i)
281 nsrc = this%recvcounts(i)
282 select type (dofs => this%recv_dof(src)%data)
283 type is (integer)
284 select case (op)
285 case (gs_op_add)
286 !OCL NORECURRENCE, NOVREC, NOALIAS
287 !DIR$ CONCURRENT
288 !DIR$ IVDEP
289 !GCC$ ivdep
290 !NEC$ IVDEP
291 !$omp do
292 do j = 1, nsrc
293 u(dofs(j)) = u(dofs(j)) + this%recv_buf(off + j)
294 end do
295 !$omp end do
296 case (gs_op_mul)
297 !OCL NORECURRENCE, NOVREC, NOALIAS
298 !DIR$ CONCURRENT
299 !DIR$ IVDEP
300 !GCC$ ivdep
301 !NEC$ IVDEP
302 !$omp do
303 do j = 1, nsrc
304 u(dofs(j)) = u(dofs(j)) * this%recv_buf(off + j)
305 end do
306 !$omp end do
307 case (gs_op_min)
308 !OCL NORECURRENCE, NOVREC, NOALIAS
309 !DIR$ CONCURRENT
310 !DIR$ IVDEP
311 !GCC$ ivdep
312 !NEC$ IVDEP
313 !$omp do
314 do j = 1, nsrc
315 u(dofs(j)) = min(u(dofs(j)), this%recv_buf(off + j))
316 end do
317 !$omp end do
318 case (gs_op_max)
319 !OCL NORECURRENCE, NOVREC, NOALIAS
320 !DIR$ CONCURRENT
321 !DIR$ IVDEP
322 !GCC$ ivdep
323 !NEC$ IVDEP
324 !$omp do
325 do j = 1, nsrc
326 u(dofs(j)) = max(u(dofs(j)), this%recv_buf(off + j))
327 end do
328 !$omp end do
329 case default
330 call neko_error("Unknown operation in gs_nbwait_neighbour")
331 end select
332 end select
333 end do
334
335 end subroutine gs_nbwait_neighbour
336
341 subroutine gs_nbsend_vec_neighbour(this, u, n, nc, tag, deps, strm)
342 class(gs_neighbour_t), intent(inout) :: this
343 integer, intent(in) :: n, nc
344 real(kind=rp), dimension(nc*n), intent(inout) :: u
345 integer, intent(in) :: tag
346 type(c_ptr), intent(inout) :: deps
347 type(c_ptr), intent(inout) :: strm
348 integer :: i, j, c, dst, off, ndst, ierr
349
350 !$omp do
351 do i = 1, size(this%send_pe)
352 dst = this%send_pe(i)
353 off = this%sdispls(i)
354 ndst = this%sendcounts(i)
355 select type (dofs => this%send_dof(dst)%data)
356 type is (integer)
357 do c = 1, nc
358 !$omp simd
359 do j = 1, ndst
360 this%send_buf_v(nc*off + (c-1)*ndst + j) = u((c-1)*n + dofs(j))
361 end do
362 end do
363 end select
364 end do
365 !$omp end do
366
367 ! A neighbourhood collective is one call over neigh_comm and must be
368 ! issued by a single thread. The end-do barrier above guarantees the
369 ! send buffer is fully packed first.
370 !$omp master
371 do i = 1, size(this%send_pe)
372 this%sendcounts_v(i) = nc * this%sendcounts(i)
373 this%sdispls_v(i) = nc * this%sdispls(i)
374 end do
375 do i = 1, size(this%recv_pe)
376 this%recvcounts_v(i) = nc * this%recvcounts(i)
377 this%rdispls_v(i) = nc * this%rdispls(i)
378 end do
379 call mpi_ineighbor_alltoallv(this%send_buf_v, this%sendcounts_v, &
380 this%sdispls_v, mpi_real_precision, this%recv_buf_v, &
381 this%recvcounts_v, this%rdispls_v, mpi_real_precision, &
382 this%neigh_comm, this%request, ierr)
383 !$omp end master
384
385 end subroutine gs_nbsend_vec_neighbour
386
388 subroutine gs_nbrecv_vec_neighbour(this, tag, nc)
389 class(gs_neighbour_t), intent(inout) :: this
390 integer, intent(in) :: tag, nc
391 ! Nothing to do: the collective is launched in nbsend_vec.
392 end subroutine gs_nbrecv_vec_neighbour
393
397 subroutine gs_nbwait_vec_neighbour(this, u, n, nc, op, strm)
398 class(gs_neighbour_t), intent(inout) :: this
399 integer, intent(in) :: n, nc
400 real(kind=rp), dimension(nc*n), intent(inout) :: u
401 type(c_ptr), intent(inout) :: strm
402 integer :: i, j, c, src, off, nsrc, ierr
403 integer :: op
404
405 !$omp master
406 call mpi_wait(this%request, mpi_status_ignore, ierr)
407 !$omp end master
408 !$omp barrier
409
410 ! Serial over peers (a dof shared by 3+ ranks appears in several recv
411 ! lists); parallelism is taken within each slab.
412 do i = 1, size(this%recv_pe)
413 src = this%recv_pe(i)
414 off = this%rdispls(i)
415 nsrc = this%recvcounts(i)
416 select type (dofs => this%recv_dof(src)%data)
417 type is (integer)
418 select case (op)
419 case (gs_op_add)
420 !$omp do
421 do j = 1, nsrc
422 do c = 1, nc
423 u((c-1)*n + dofs(j)) = u((c-1)*n + dofs(j)) + &
424 this%recv_buf_v(nc*off + (c-1)*nsrc + j)
425 end do
426 end do
427 !$omp end do
428 case (gs_op_mul)
429 !$omp do
430 do j = 1, nsrc
431 do c = 1, nc
432 u((c-1)*n + dofs(j)) = u((c-1)*n + dofs(j)) * &
433 this%recv_buf_v(nc*off + (c-1)*nsrc + j)
434 end do
435 end do
436 !$omp end do
437 case (gs_op_min)
438 !$omp do
439 do j = 1, nsrc
440 do c = 1, nc
441 u((c-1)*n + dofs(j)) = min(u((c-1)*n + dofs(j)), &
442 this%recv_buf_v(nc*off + (c-1)*nsrc + j))
443 end do
444 end do
445 !$omp end do
446 case (gs_op_max)
447 !$omp do
448 do j = 1, nsrc
449 do c = 1, nc
450 u((c-1)*n + dofs(j)) = max(u((c-1)*n + dofs(j)), &
451 this%recv_buf_v(nc*off + (c-1)*nsrc + j))
452 end do
453 end do
454 !$omp end do
455 case default
456 call neko_error("Unknown operation in gs_nbwait_vec_neighbour")
457 end select
458 end select
459 end do
460
461 end subroutine gs_nbwait_vec_neighbour
462
463end 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:54
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
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:50
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_init_vec(this)
Allocate the fused vector exchange buffers and the nc-scaled collective descriptors,...
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:14
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:53
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