Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_crystal_plan.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!
76 use comm, only : pe_rank, pe_size, neko_comm
77 use stack, only : stack_i4_t
78 use utils, only : neko_error
79 use mpi_f08, only : mpi_sendrecv, mpi_status, mpi_integer, mpi_proc_null
80 implicit none
81 private
82
85 integer, parameter :: cr_rec = 3
86
89 integer, parameter :: cr_plan_tag = 0
90
99 type, public :: gs_crystal_stage_t
101 integer :: dst = -1
103 integer :: src = -1
105 integer :: src2 = -1
106 integer :: nsw = 0
107 integer :: nrw = 0
108 integer :: nr2w = 0
109 integer :: nkw = 0
110 integer :: src_sel = 1
111 integer :: dst_sel = 1
114 logical :: inplace = .false.
118 integer, allocatable :: keep_idx(:)
122 integer, allocatable :: send_idx(:)
123 end type gs_crystal_stage_t
124
126 type, public :: gs_crystal_plan_t
129 type(gs_crystal_stage_t), allocatable :: stage(:)
130 integer :: nstage = 0
131 integer :: nwrk = 0
132 integer :: nsmax = 0
133 integer :: ntotal = 0
134 integer :: nfinal = 0
135 integer :: final_sel = 1
138 integer, allocatable :: pack_keep_dof(:)
141 integer, allocatable :: pack_send_dof(:)
144 integer, allocatable :: unpack_dof(:)
146 integer, allocatable :: final_off(:)
148 integer, allocatable :: final_len(:)
149 integer :: nfinal_rec = 0
150 contains
151 procedure, pass(this) :: init => gs_crystal_plan_init
152 procedure, pass(this) :: free => gs_crystal_plan_free
153 end type gs_crystal_plan_t
154
155contains
156
167 subroutine gs_crystal_plan_init(this, send_pe, recv_pe, send_dof, recv_dof)
168 class(gs_crystal_plan_t), intent(inout) :: this
169 integer, intent(in) :: send_pe(:), recv_pe(:)
170 type(stack_i4_t), intent(inout) :: send_dof(0:), recv_dof(0:)
171 type(gs_crystal_stage_t), allocatable :: st(:)
172 logical, allocatable :: active(:)
173 integer, allocatable :: rdest(:), rorig(:), rlen(:)
174 integer, allocatable :: ndest(:), norig(:), nlen(:)
175 integer, allocatable :: smeta(:), rmeta(:), rmeta2(:)
176 integer, allocatable :: kidx(:), sidx(:), pack_dof(:)
177 integer :: lo, hi, m, half, mid, r, partner
178 integer :: s, nst, nstmax, sel, nrec, nnew, off, t
179 integer :: i, j, k, nkw, nsw, nrw, nr2w, nsrec, nrrec, nr2rec
180 integer :: dst, src, src2, curlen
181 logical :: lower, keepit
182
183 call this%free()
184
185 ! The records this rank starts with: one per peer, holding that peer's
186 ! whole slab of shared dofs
187 nrec = size(send_pe)
188 allocate(rdest(max(nrec, 1)), rorig(max(nrec, 1)), rlen(max(nrec, 1)))
189 this%ntotal = 0
190 do i = 1, nrec
191 rdest(i) = send_pe(i)
192 rorig(i) = pe_rank
193 rlen(i) = send_dof(send_pe(i))%size()
194 this%ntotal = this%ntotal + rlen(i)
195 end do
196 this%nwrk = this%ntotal
197
198 ! The bisection visits ceil(log2(pe_size)) ranges; size the scratch plan
199 ! for that many stages before any of them are dropped
200 nstmax = 0
201 m = pe_size
202 do while (m .gt. 1)
203 nstmax = nstmax + 1
204 m = m - m/2
205 end do
206 allocate(st(max(nstmax, 1)), active(max(nstmax, 1)))
207 active = .false.
208
209 lo = 0
210 hi = pe_size
211 s = 0
212
213 do while (hi - lo .gt. 1)
214 s = s + 1
215 m = hi - lo
216 half = m / 2
217 mid = lo + half
218 lower = (pe_rank .lt. mid)
219
220 ! Partner in the opposite half. Lower ranks always have one; in an
221 ! odd sized range the last upper rank is unpaired and offloads its
222 ! cross-half records to lo, which absorbs them with an extra receive
223 if (lower) then
224 partner = mid + (pe_rank - lo)
225 else
226 r = pe_rank - mid
227 if (r .lt. half) then
228 partner = lo + r
229 else
230 partner = -1
231 end if
232 end if
233
234 dst = partner
235 src = partner
236 src2 = -1
237 if (iand(m, 1) .eq. 1) then
238 if ((.not. lower) .and. ((pe_rank - mid) .eq. half)) then
239 dst = lo
240 else if (pe_rank .eq. lo) then
241 src2 = hi - 1
242 end if
243 end if
244
245 ! Split the records into those already in our half and those bound
246 ! for the other one, and note where their words sit in the buffer
247 nkw = 0
248 nsw = 0
249 nsrec = 0
250 do i = 1, nrec
251 keepit = ((rdest(i) .lt. mid) .eqv. lower)
252 if (keepit) then
253 nkw = nkw + rlen(i)
254 else
255 nsw = nsw + rlen(i)
256 nsrec = nsrec + 1
257 end if
258 end do
259
260 allocate(kidx(max(nkw, 1)), sidx(max(nsw, 1)))
261 allocate(smeta(max(cr_rec*nsrec, 1)))
262 off = 0
263 j = 0
264 k = 0
265 nsrec = 0
266 do i = 1, nrec
267 keepit = ((rdest(i) .lt. mid) .eqv. lower)
268 if (keepit) then
269 do t = 1, rlen(i)
270 j = j + 1
271 kidx(j) = off + t
272 end do
273 else
274 do t = 1, rlen(i)
275 k = k + 1
276 sidx(k) = off + t
277 end do
278 smeta(cr_rec*nsrec + 1) = rdest(i)
279 smeta(cr_rec*nsrec + 2) = rorig(i)
280 smeta(cr_rec*nsrec + 3) = rlen(i)
281 nsrec = nsrec + 1
282 end if
283 off = off + rlen(i)
284 end do
285
286 ! Hand the outgoing records' metadata to the partner and take in what
287 ! it routes to us, then, in an odd sized range, the unpaired rank's
288 call cr_meta_exchange(smeta, nsrec, dst, rmeta, nrrec, src)
289 nr2rec = 0
290 if (src2 .ge. 0) then
291 call cr_meta_exchange(smeta, 0, -1, rmeta2, nr2rec, src2)
292 else
293 allocate(rmeta2(1))
294 end if
295
296 nrw = 0
297 do i = 1, nrrec
298 nrw = nrw + rmeta(cr_rec*(i-1) + 3)
299 end do
300 nr2w = 0
301 do i = 1, nr2rec
302 nr2w = nr2w + rmeta2(cr_rec*(i-1) + 3)
303 end do
304
305 ! Rebuild the record list as (kept) ++ (from src) ++ (from src2), the
306 ! order the words take in the buffer after this stage
307 nnew = (nrec - nsrec) + nrrec + nr2rec
308 allocate(ndest(max(nnew, 1)), norig(max(nnew, 1)), nlen(max(nnew, 1)))
309 j = 0
310 do i = 1, nrec
311 if ((rdest(i) .lt. mid) .eqv. lower) then
312 j = j + 1
313 ndest(j) = rdest(i)
314 norig(j) = rorig(i)
315 nlen(j) = rlen(i)
316 end if
317 end do
318 do i = 1, nrrec
319 j = j + 1
320 ndest(j) = rmeta(cr_rec*(i-1) + 1)
321 norig(j) = rmeta(cr_rec*(i-1) + 2)
322 nlen(j) = rmeta(cr_rec*(i-1) + 3)
323 end do
324 do i = 1, nr2rec
325 j = j + 1
326 ndest(j) = rmeta2(cr_rec*(i-1) + 1)
327 norig(j) = rmeta2(cr_rec*(i-1) + 2)
328 nlen(j) = rmeta2(cr_rec*(i-1) + 3)
329 end do
330
331 ! A stage with nothing on the wire in either direction keeps every
332 ! word where it is, and is dropped from the plan below
333 if (nsw .eq. 0) dst = -1
334 if (nrw .eq. 0) src = -1
335 if (nr2w .eq. 0) src2 = -1
336
337 st(s)%dst = dst
338 st(s)%src = src
339 st(s)%src2 = src2
340 st(s)%nsw = nsw
341 st(s)%nrw = nrw
342 st(s)%nr2w = nr2w
343 st(s)%nkw = nkw
344 st(s)%inplace = (nsw .eq. 0)
345 call move_alloc(kidx, st(s)%keep_idx)
346 call move_alloc(sidx, st(s)%send_idx)
347 active(s) = (dst .ge. 0) .or. (src .ge. 0) .or. (src2 .ge. 0)
348
349 this%nsmax = max(this%nsmax, nsw)
350 curlen = nkw + nrw + nr2w
351 this%nwrk = max(this%nwrk, curlen)
352
353 deallocate(smeta, rmeta, rmeta2)
354 call move_alloc(ndest, rdest)
355 call move_alloc(norig, rorig)
356 call move_alloc(nlen, rlen)
357 nrec = nnew
358
359 if (lower) then
360 hi = mid
361 else
362 lo = mid
363 end if
364 end do
365
366 ! Everything left is addressed to this rank; its origin says which of
367 ! the recv_dof lists it reduces into
368 call cr_final_dofs(this, rdest, rorig, rlen, nrec, recv_pe, recv_dof)
369
370 ! Keep only the stages that move something, and pin down which buffer
371 ! column each of them reads and writes
372 nst = 0
373 do i = 1, s
374 if (active(i)) nst = nst + 1
375 end do
376 allocate(this%stage(max(nst, 1)))
377 this%nstage = nst
378 j = 0
379 do i = 1, s
380 if (.not. active(i)) cycle
381 j = j + 1
382 this%stage(j)%dst = st(i)%dst
383 this%stage(j)%src = st(i)%src
384 this%stage(j)%src2 = st(i)%src2
385 this%stage(j)%nsw = st(i)%nsw
386 this%stage(j)%nrw = st(i)%nrw
387 this%stage(j)%nr2w = st(i)%nr2w
388 this%stage(j)%nkw = st(i)%nkw
389 this%stage(j)%inplace = st(i)%inplace
390 call move_alloc(st(i)%keep_idx, this%stage(j)%keep_idx)
391 call move_alloc(st(i)%send_idx, this%stage(j)%send_idx)
392 end do
393 deallocate(st, active)
394
395 ! The first stage materialises its words out of the shared vector, so it
396 ! writes column 1 whether or not it would otherwise have swapped
397 sel = 1
398 if (nst .gt. 0) then
399 this%stage(1)%src_sel = 1
400 this%stage(1)%dst_sel = 1
401 do j = 2, nst
402 this%stage(j)%src_sel = sel
403 if (this%stage(j)%inplace) then
404 this%stage(j)%dst_sel = sel
405 else
406 this%stage(j)%dst_sel = 3 - sel
407 sel = 3 - sel
408 end if
409 end do
410 end if
411 this%final_sel = sel
412
413 ! Turn the first stage's buffer positions into shared vector indices, so
414 ! its words are gathered straight out of the shared vector
415 if (nst .gt. 0) then
416 allocate(pack_dof(max(this%ntotal, 1)))
417 j = 0
418 do i = 1, size(send_pe)
419 select type (sp => send_dof(send_pe(i))%data)
420 type is (integer)
421 do t = 1, send_dof(send_pe(i))%size()
422 j = j + 1
423 pack_dof(j) = sp(t)
424 end do
425 end select
426 end do
427
428 allocate(this%pack_keep_dof(max(this%stage(1)%nkw, 1)))
429 do j = 1, this%stage(1)%nkw
430 this%pack_keep_dof(j) = pack_dof(this%stage(1)%keep_idx(j))
431 end do
432 allocate(this%pack_send_dof(max(this%stage(1)%nsw, 1)))
433 do j = 1, this%stage(1)%nsw
434 this%pack_send_dof(j) = pack_dof(this%stage(1)%send_idx(j))
435 end do
436 deallocate(pack_dof)
437 deallocate(this%stage(1)%keep_idx, this%stage(1)%send_idx)
438 else
439 allocate(this%pack_keep_dof(1), this%pack_send_dof(1))
440 end if
441
442 this%nwrk = max(this%nwrk, 1)
443 this%nsmax = max(this%nsmax, 1)
444
445 deallocate(rdest, rorig, rlen)
446
447 end subroutine gs_crystal_plan_init
448
455 subroutine cr_final_dofs(this, rdest, rorig, rlen, nrec, recv_pe, recv_dof)
456 class(gs_crystal_plan_t), intent(inout) :: this
457 integer, intent(in) :: rdest(:), rorig(:), rlen(:), nrec
458 integer, intent(in) :: recv_pe(:)
459 type(stack_i4_t), intent(inout) :: recv_dof(0:)
460 integer :: i, j, t, nfin
461
462 if (nrec .ne. size(recv_pe)) then
463 call neko_error('gs_crystal_plan: routed record count does not ' // &
464 'match the gather-scatter schedule')
465 end if
466
467 nfin = 0
468 do i = 1, nrec
469 if (rdest(i) .ne. pe_rank) then
470 call neko_error('gs_crystal_plan: record left undelivered')
471 end if
472 if (rorig(i) .lt. 0 .or. rorig(i) .ge. pe_size) then
473 call neko_error('gs_crystal_plan: record origin out of range')
474 end if
475 if (rlen(i) .ne. recv_dof(rorig(i))%size()) then
476 call neko_error('gs_crystal_plan: routed record length does ' // &
477 'not match the gather-scatter schedule')
478 end if
479 nfin = nfin + rlen(i)
480 end do
481
482 this%nfinal = nfin
483 this%nfinal_rec = nrec
484 this%nwrk = max(this%nwrk, nfin)
485 allocate(this%unpack_dof(max(nfin, 1)))
486 allocate(this%final_off(max(nrec, 1)), this%final_len(max(nrec, 1)))
487
488 j = 0
489 do i = 1, nrec
490 this%final_off(i) = j
491 this%final_len(i) = rlen(i)
492 select type (rp_dof => recv_dof(rorig(i))%data)
493 type is (integer)
494 do t = 1, rlen(i)
495 j = j + 1
496 this%unpack_dof(j) = rp_dof(t)
497 end do
498 end select
499 end do
500
501 end subroutine cr_final_dofs
502
513 subroutine cr_meta_exchange(sbuf, nsrec, dst, rbuf, nrrec, src)
514 integer, intent(in) :: sbuf(:)
515 integer, intent(in) :: nsrec, dst, src
516 integer, allocatable, intent(out) :: rbuf(:)
517 integer, intent(out) :: nrrec
518 type(mpi_status) :: status
519 integer :: ierr, d, s
520
521 d = mpi_proc_null
522 if (dst .ge. 0) d = dst
523 s = mpi_proc_null
524 if (src .ge. 0) s = src
525
526 nrrec = 0
527 call mpi_sendrecv(nsrec, 1, mpi_integer, d, cr_plan_tag, &
528 nrrec, 1, mpi_integer, s, cr_plan_tag, neko_comm, status, ierr)
529
530 allocate(rbuf(max(cr_rec*nrrec, 1)))
531 call mpi_sendrecv(sbuf, cr_rec*nsrec, mpi_integer, d, cr_plan_tag, &
532 rbuf, cr_rec*nrrec, mpi_integer, s, cr_plan_tag, neko_comm, &
533 status, ierr)
534
535 end subroutine cr_meta_exchange
536
538 subroutine gs_crystal_plan_free(this)
539 class(gs_crystal_plan_t), intent(inout) :: this
540 integer :: i
541
542 if (allocated(this%stage)) then
543 do i = 1, size(this%stage)
544 if (allocated(this%stage(i)%keep_idx)) &
545 deallocate(this%stage(i)%keep_idx)
546 if (allocated(this%stage(i)%send_idx)) &
547 deallocate(this%stage(i)%send_idx)
548 end do
549 deallocate(this%stage)
550 end if
551
552 if (allocated(this%pack_keep_dof)) deallocate(this%pack_keep_dof)
553 if (allocated(this%pack_send_dof)) deallocate(this%pack_send_dof)
554 if (allocated(this%unpack_dof)) deallocate(this%unpack_dof)
555 if (allocated(this%final_off)) deallocate(this%final_off)
556 if (allocated(this%final_len)) deallocate(this%final_len)
557
558 this%nstage = 0
559 this%nwrk = 0
560 this%nsmax = 0
561 this%ntotal = 0
562 this%nfinal = 0
563 this%nfinal_rec = 0
564 this%final_sel = 1
565
566 end subroutine gs_crystal_plan_free
567
568end module gs_crystal_plan
Definition comm.F90:1
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
Routing plan for the crystal router gather-scatter comm. backends.
subroutine gs_crystal_plan_init(this, send_pe, recv_pe, send_dof, recv_dof)
Work out the routing plan for a gather-scatter schedule.
integer, parameter cr_rec
Metadata words per in-flight record in the symbolic pass: destination rank, originating rank,...
integer, parameter cr_plan_tag
Message tag for the symbolic pass. Setup only, and every exchange in it is a fully synchronised MPI_S...
subroutine cr_meta_exchange(sbuf, nsrec, dst, rbuf, nrrec, src)
Size-negotiated bidirectional exchange of record metadata with a partner. Either dst or src may be -1...
subroutine gs_crystal_plan_free(this)
Release the routing plan.
subroutine cr_final_dofs(this, rdest, rorig, rlen, nrec, recv_pe, recv_dof)
Turn the records left at the end of the routing into the reduction order of the delivered words.
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
The full routing plan for one gather-scatter schedule.
One active communication stage of the routing plan.
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40