!> @file implicit_gather_scatter.F90 !> @brief Gather / scatter helpers used by the Phase E implicit-stepper !! gather-solve-scatter wrapper. !! !! Hybrid cpp split mirrors src/parallel_reductions.F90 and !! src/solution_gather.F90: MS-MPI ships only the legacy `use mpi` !! module; OpenMPI ships both and we prefer `mpi_f08` for strong typing. module implicit_gather_scatter use precision, only: wp use solver_state, only: neq use domain_decomposition, only: decomp_t, rank_local_count #ifdef CFD_SOLVER_LEGACY_MPI use mpi, only: MPI_Gatherv, MPI_Scatterv, MPI_DOUBLE_PRECISION #else use mpi_f08, only: MPI_Gatherv, MPI_Scatterv, MPI_DOUBLE_PRECISION #endif use mpi_runtime, only: mpi_world, my_rank, n_ranks, parallel_fatal implicit none private public :: gather_field_to_root public :: scatter_field_from_root contains !> Gather (neq, n_local) slices from every rank into (neq, n_global) !! on rank 0 via MPI_Gatherv. On non-root ranks `gather_buf` is not !! accessed; pass a `(neq, 0)` sentinel allocation. subroutine gather_field_to_root(local_field, n_local, decomp, gather_buf) real(wp), intent(in) :: local_field(:, :) integer, intent(in) :: n_local type(decomp_t), intent(in) :: decomp real(wp), intent(inout) :: gather_buf(:, :) integer, allocatable :: recv_counts(:), displs(:) integer :: send_count, ierr, info call build_counts_displs(decomp, recv_counts, displs) send_count = neq * n_local call MPI_Gatherv(local_field, send_count, MPI_DOUBLE_PRECISION, & gather_buf, recv_counts, displs, MPI_DOUBLE_PRECISION, & 0, mpi_world(), ierr) if (ierr /= 0) call parallel_fatal('gather_field_to_root: MPI_Gatherv failed') deallocate (recv_counts, displs, stat=info) if (info /= 0) call parallel_fatal('gather_field_to_root: deallocation failed') end subroutine gather_field_to_root !> Scatter (neq, n_global) on rank 0 into per-rank (neq, n_local) !! slices via MPI_Scatterv. On non-root ranks `gather_buf` is not !! accessed; pass a `(neq, 0)` sentinel allocation. subroutine scatter_field_from_root(gather_buf, decomp, local_field, n_local) real(wp), intent(in) :: gather_buf(:, :) type(decomp_t), intent(in) :: decomp real(wp), intent(inout) :: local_field(:, :) integer, intent(in) :: n_local integer, allocatable :: send_counts(:), displs(:) integer :: recv_count, ierr, info call build_counts_displs(decomp, send_counts, displs) recv_count = neq * n_local call MPI_Scatterv(gather_buf, send_counts, displs, MPI_DOUBLE_PRECISION, & local_field, recv_count, MPI_DOUBLE_PRECISION, & 0, mpi_world(), ierr) if (ierr /= 0) call parallel_fatal('scatter_field_from_root: MPI_Scatterv failed') deallocate (send_counts, displs, stat=info) if (info /= 0) call parallel_fatal('scatter_field_from_root: deallocation failed') end subroutine scatter_field_from_root ! Build per-rank counts and displacements from the same slab partition ! arithmetic decompose() uses. Both gather and scatter consume this same ! layout; non-root passes zero-size sentinel allocations so MPI gets ! valid pointer arguments. subroutine build_counts_displs(decomp, counts, displs) type(decomp_t), intent(in) :: decomp integer, allocatable, intent(out) :: counts(:), displs(:) integer :: rk, nr, info nr = n_ranks() if (my_rank() == 0) then allocate (counts(0:nr - 1), displs(0:nr - 1), stat=info) if (info /= 0) call parallel_fatal('build_counts_displs: allocation failed') do rk = 0, nr - 1 counts(rk) = neq * rank_local_count(decomp % n_global, nr, rk) end do displs(0) = 0 do rk = 1, nr - 1 displs(rk) = displs(rk - 1) + counts(rk - 1) end do else ! Non-root must still pass valid pointers to MPI_Gatherv / ! MPI_Scatterv; allocate zero-length so the addresses are valid ! even though they are unused. allocate (counts(0:0), displs(0:0), stat=info) if (info /= 0) call parallel_fatal('build_counts_displs: allocation failed') counts = 0 displs = 0 end if end subroutine build_counts_displs end module implicit_gather_scatter