!> @file domain_decomposition_2d.f90 !> @brief Pure 2D block decomposition of the Cartesian grid across an MPI process grid. !! Neighbour ranks are supplied by the caller (from MPI_Cart_shift) so this stays pure !! and unit-testable with synthetic coordinates. module domain_decomposition_2d use precision, only: wp use mpi_runtime, only: parallel_fatal use domain_decomposition, only: rank_local_count, rank_first_global implicit none private public :: decomp_2d_t, decompose_2d, is_decomp_2d_feasible, validate_decomp_2d type :: decomp_2d_t integer :: coord_x = -1, coord_y = -1 integer :: dim_x = -1, dim_y = -1 integer :: nx_global = -1, ny_global = -1 integer :: nx_local = -1, ny_local = -1 integer :: ix_first_global = -1, ix_last_global = -1 integer :: iy_first_global = -1, iy_last_global = -1 integer :: halo_width = -1 integer :: x_lo_neighbour = -1, x_hi_neighbour = -1 integer :: y_lo_neighbour = -1, y_hi_neighbour = -1 logical :: periodic_x = .false., periodic_y = .false. end type decomp_2d_t contains pure function decompose_2d(coords, dims, nx_global, ny_global, halo_width, & periodic_x, periodic_y, x_lo, x_hi, y_lo, y_hi) result(d) integer, intent(in) :: coords(2), dims(2), nx_global, ny_global, halo_width logical, intent(in) :: periodic_x, periodic_y integer, intent(in) :: x_lo, x_hi, y_lo, y_hi type(decomp_2d_t) :: d d % coord_x = coords(1); d % coord_y = coords(2) d % dim_x = dims(1); d % dim_y = dims(2) d % nx_global = nx_global; d % ny_global = ny_global d % halo_width = halo_width d % periodic_x = periodic_x; d % periodic_y = periodic_y d % x_lo_neighbour = x_lo; d % x_hi_neighbour = x_hi d % y_lo_neighbour = y_lo; d % y_hi_neighbour = y_hi call axis_split(coords(1), dims(1), nx_global, d % nx_local, d % ix_first_global, d % ix_last_global) call axis_split(coords(2), dims(2), ny_global, d % ny_local, d % iy_first_global, d % iy_last_global) end function decompose_2d !> Split one Cartesian axis (`n_global` cells across `dim` process slots) for !! the slot at `coord`. Delegates to the shared 1D slab-partition helpers so !! the per-axis block layout matches the 1D `decompose()` exactly (single !! source of truth: domain_decomposition%rank_local_count/rank_first_global). pure subroutine axis_split(coord, dim, n_global, n_local, i_first, i_last) integer, intent(in) :: coord, dim, n_global integer, intent(out) :: n_local, i_first, i_last n_local = rank_local_count(n_global, dim, coord) i_first = rank_first_global(n_global, dim, coord) i_last = i_first + n_local - 1 end subroutine axis_split pure function is_decomp_2d_feasible(d) result(ok) type(decomp_2d_t), intent(in) :: d logical :: ok ok = d % nx_local >= d % halo_width .and. d % ny_local >= d % halo_width end function is_decomp_2d_feasible !> Abort with a clear message if either local tile dimension is too small to !! hold the halo cells the chosen reconstruction scheme requires. Mirrors the !! 1D `validate_decomp`; called from the 2D driver right after the !! decomposition is built (an over-fine MPI process grid, e.g. a thin axis !! from MPI_Dims_create, would otherwise silently over-read halos). subroutine validate_decomp_2d(d) type(decomp_2d_t), intent(in) :: d character(len=256) :: msg if (.not. is_decomp_2d_feasible(d)) then write (msg, '(A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A)') & 'decomp_2d: local tile (', d % nx_local, ' x ', d % ny_local, & ') < halo_width (', d % halo_width, & ') at cart coord (', d % coord_x, ',', d % coord_y, & ') of process grid ', d % dim_x, ' x ', d % dim_y, & '; use fewer ranks or a larger nx/ny' call parallel_fatal(trim(msg)) end if end subroutine validate_decomp_2d end module domain_decomposition_2d