!> @file halo_exchange.f90 !> @brief Non-blocking halo exchange for solver_state_t%ub. !! !! `exchange_halos` posts MPI_Irecv from each neighbour into this rank's !! halo cells, then MPI_Isend the matching interior slices, then !! MPI_Waitall on all four requests. Boundary ranks (with neighbour !! MPI_PROC_NULL) have those Isend/Irecv calls degenerate into silent !! no-ops by MPI standard, so the same code path runs at -np 1. !! !! Only one buffer set (state%ub) is halo-exchanged today. If more halo'd !! arrays are ever added, generalize this module rather than duplicating the !! request-management logic. module halo_exchange use solver_state, only: solver_state_t, neq use domain_decomposition, only: decomp_t use mpi_runtime, only: mpi_world, parallel_fatal use option_registry, only: method_fvm #ifdef CFD_SOLVER_LEGACY_MPI use mpi, only: MPI_Isend, MPI_Irecv, MPI_Waitall, MPI_DOUBLE_PRECISION, & MPI_STATUSES_IGNORE #else use mpi_f08, only: MPI_Isend, MPI_Irecv, MPI_Waitall, MPI_DOUBLE_PRECISION, & MPI_STATUSES_IGNORE, MPI_Request #endif implicit none private public :: exchange_halos integer, parameter :: TAG_HALO_L = 1001 integer, parameter :: TAG_HALO_R = 1002 contains !> Fill state%ub's halo cells from neighbours. subroutine exchange_halos(state, d) type(solver_state_t), intent(inout) :: state type(decomp_t), intent(in) :: d #ifdef CFD_SOLVER_LEGACY_MPI integer :: requests(4) #else type(MPI_Request) :: requests(4) #endif integer :: ierr, cnt, n_local, h, lsend, rsend h = d % halo_width n_local = d % n_local cnt = neq * h ! Interior-side slices sent to neighbours: the first h cells go left, the ! last h cells go right. lsend = 1 rsend = n_local - h + 1 ! Periodic-wrap correction (multi-rank only). This is specific to the ! NODAL (FDM) grid, which has a duplicated endpoint: global node n_global ! coincides with global node 1 (x_R == x_L under periodicity). The single- ! rank wrap in apply_bcs collapses this duplicate (period = n_pt - 1). The ! cross-rank wrap must match: the periodic-edge ranks therefore skip their ! duplicated boundary node when sending across the wrap, shifting the sent ! slice by one cell. Without this, rank 0's upwind (x_L) halo is filled from ! the duplicate node instead of its true periodic neighbour, an off-by-one ! that corrupts the boundary solution. ! ! The cell-centered (FVM) grid has NO duplicated endpoint (cells, not nodes, ! tile the domain), so this shift MUST NOT be applied there: the FVM periodic ! neighbour of global cell 1 is global cell n_global and the slice is sent ! unshifted. Applying the nodal skip to FVM produces off-by-one wrap halos, ! making the inter-rank periodic-face flux inconsistent between the two ! ranks, which breaks discrete conservation at np > 1. Gate on method so the ! FDM path stays byte-identical (block_t % method defaults to 'fdm', so any ! non-FVM caller keeps the nodal correction). if (trim(state % blocks(1) % method) /= method_fvm .and. & d % is_periodic .and. d % n_ranks > 1) then if (d % my_rank == 0) lsend = 2 ! skip node 1 (== global last) if (d % my_rank == d % n_ranks - 1) rsend = n_local - h ! skip node n_local (== global 1) end if ! Post receives first so sends don't deadlock on small MPIs. call MPI_Irecv(state % ub(1, 1 - h), cnt, MPI_DOUBLE_PRECISION, & d % left_neighbour, TAG_HALO_R, mpi_world(), requests(1), ierr) if (ierr /= 0) call parallel_fatal('halo_exchange: Irecv left failed') call MPI_Irecv(state % ub(1, n_local + 1), cnt, MPI_DOUBLE_PRECISION, & d % right_neighbour, TAG_HALO_L, mpi_world(), requests(2), ierr) if (ierr /= 0) call parallel_fatal('halo_exchange: Irecv right failed') call MPI_Isend(state % ub(1, lsend), cnt, MPI_DOUBLE_PRECISION, & d % left_neighbour, TAG_HALO_L, mpi_world(), requests(3), ierr) if (ierr /= 0) call parallel_fatal('halo_exchange: Isend left failed') call MPI_Isend(state % ub(1, rsend), cnt, MPI_DOUBLE_PRECISION, & d % right_neighbour, TAG_HALO_R, mpi_world(), requests(4), ierr) if (ierr /= 0) call parallel_fatal('halo_exchange: Isend right failed') call MPI_Waitall(4, requests, MPI_STATUSES_IGNORE, ierr) if (ierr /= 0) call parallel_fatal('halo_exchange: Waitall failed') end subroutine exchange_halos end module halo_exchange