Ocean atmospheric surface-pressure loading state (inverse barometer).
!! Ocean atmospheric surface-pressure loading state (inverse barometer). module rdb_ocean_p_surf !! Surface-pressure loading for the ocean C-grid dyn-core (PR-17). An !! atmospheric surface pressure `p_surf(x,y)` (Pa) adds a depth-uniform !! acceleration `-(1/rho0) grad(p_surf)` to the horizontal momentum !! (Wunsch & Stammer 1997). Because the term is depth-independent it is !! a purely BAROTROPIC forcing: it drives the free surface and, at rest, !! integrates to the equilibrium inverse-barometer response !! `eta_ib = -p_surf/(rho0 g) + const` (~1 cm depression per hPa of !! atmospheric high; Ponte 2006 for the closed-domain mean removal). !! !! It lands on the SAME `eta_forcing` seam the equilibrium body tide and !! scalar SAL compose through: the barotropic substep drives !! `-g grad(eta - eta_forcing)`, so folding !! `eta_ib = -p_surf/(rho0 g_bt)` into the seam gives the momentum an !! extra `-g grad(eta) - (1/rho0) grad(p_surf)` exactly. `eta_ib` and the !! combined seam field `eta_seam = eta_ib [+ eta_tide]` are cell-centred !! 2-D fields in metres, differenced on the identical two-point stencil !! the SSH uses, so the discrete inverse-barometer balance !! `eta = eta_ib + const` is an exact discrete steady state. !! !! Sign: `eta_ib = -p_surf/(rho0 g)` — a high depresses the surface, a low !! bulges it. Gravity is `bt_work%g_bt` (what the barotropic substep that !! consumes the seam actually runs on), NOT `rdb_constants:GRAVITY`; the !! caller passes it in so `eta_ib` scales with the ocean's own dynamics. !! !! `p_surf` (the assembled total, Pa, `>= 0`) is consumed read-only from !! the surface-forcing type (`ocean_surface_flux_t`) — this module adds no !! forcing field of its own, and does not write `p_surf` (the dyn step !! holds `sf` `intent(in)`). With no ice mass-loading the assembly !! `p_surf = p_surf_atm` is a configure-time seed (`set_p_surf_const` !! fills both) since `p_surf_atm` is static; when sea-ice loading lands !! (PR-18) its `ice_ocean_mass_load` overwrites `p_surf = p_surf_atm + !! g_load*mis` via its own `inout` access earlier in the same outer step, !! and this module reads whatever `p_surf` then holds. Adcroft et al. !! (2019) levitating surface-load convention. use rdb_constants, only: wp use rdb_grid, only: hgrid_t use, intrinsic :: iso_fortran_env, only: int64 use rdb_mem_report, only: arr_bytes implicit none private public :: ocean_p_surf_t public :: p_surf_configure, p_surf_update_seam type :: ocean_p_surf_t logical :: is_init = .false. !! True between `init` and `destroy` (tracks GPU attachment too). logical :: enable = .false. !! Master switch (default off => bit-identical). logical :: in_eos = .false. !! Also hand the assembled `p_surf` to the equation of state's !! IN-SITU pressure arguments as the top-of-column pressure !! (`&ocean_psurf_nml in_eos`). Read by the outer-step driver, !! which refreshes `multilayer_state_t%p_top` from `sf%p_surf` !! when BOTH this and `enable` are set; `p_top` is otherwise the !! zero array it is allocated as, and every EOS evaluation is !! bit-identical. It does NOT touch the potential density !! `ms%rho_layer` (uniform `eos%p_ref` by design). This slot does !! NOT own `p_top` — the multilayer state does, because that is !! what the in-situ consumers already carry. real(wp) :: rho0 = 1035.0_wp !! Boussinesq reference density (kg/m^3). Assigned from !! `ocean_state%eos%rho0` at configure — the single ρ₀ of record. real(wp), allocatable :: eta_ib(:, :) !! (nx,ny) inverse-barometer elevation `-p_surf/(rho0 g_bt)`, metres. real(wp), allocatable :: eta_seam(:, :) !! (nx,ny) combined seam field `eta_ib [+ eta_tide]` — the surface !! elevation the barotropic PGF drives `-g grad(eta - .)` against. contains procedure, non_overridable :: init => ocean_p_surf_init procedure, non_overridable :: destroy => ocean_p_surf_destroy procedure, non_overridable :: enter_data => ocean_p_surf_enter_data procedure, non_overridable :: exit_data => ocean_p_surf_exit_data procedure, non_overridable :: bytes => ocean_p_surf_bytes end type ocean_p_surf_t contains subroutine ocean_p_surf_init(this, grid) !! Minimal init — the real allocation happens in `p_surf_configure` !! once the namelist is available (host, before enter_data). class(ocean_p_surf_t), intent(inout) :: this type(hgrid_t), intent(in) :: grid if (.false.) this%rho0 = real(grid%nx_total, wp) this%is_init = .true. end subroutine ocean_p_surf_init subroutine ocean_p_surf_destroy(this) class(ocean_p_surf_t), intent(inout) :: this this%is_init = .false. if (allocated(this%eta_ib)) deallocate (this%eta_ib) if (allocated(this%eta_seam)) deallocate (this%eta_seam) end subroutine ocean_p_surf_destroy subroutine p_surf_configure(this, nx, ny) !! Allocate `eta_ib`/`eta_seam` (host, before enter_data). No-op when !! `.not. enable` (bit-identical; zero extra device memory). Mirrors !! `tides_configure_astronomy`. class(ocean_p_surf_t), intent(inout) :: this integer, intent(in) :: nx, ny if (.not. this%enable) return if (allocated(this%eta_ib)) deallocate (this%eta_ib) if (allocated(this%eta_seam)) deallocate (this%eta_seam) allocate (this%eta_ib(nx, ny), source=0.0_wp) allocate (this%eta_seam(nx, ny), source=0.0_wp) end subroutine p_surf_configure subroutine p_surf_update_seam(this, p_surf, g_bt, eta_tide) !! Refresh the combined seam field `eta_seam` for the current outer !! step from the assembled total surface pressure `p_surf` (read-only; !! see the module header for where the assembly happens): fold !! `eta_ib = -p_surf/(rho0 g_bt)` into `eta_seam = eta_ib [+ eta_tide]`. !! `g_bt` is the barotropic-substep gravity (`bt_work%g_bt`), passed in !! at call time so the seam scales with the ocean's own dynamics. !! `eta_tide` (optional) is the equilibrium-tide + scalar-SAL seam, !! added when the tide is on. Host does one reciprocal; the fill is a !! single explicit-shape `do concurrent`. `p_surf` is the !! device-resident `ocean_surface_flux_t%p_surf` component array. class(ocean_p_surf_t), intent(inout) :: this real(wp), intent(in) :: p_surf(:, :) real(wp), intent(in) :: g_bt real(wp), intent(in), optional :: eta_tide(:, :) real(wp) :: i_rho0_g integer :: nx, ny nx = size(this%eta_ib, 1) ny = size(this%eta_ib, 2) i_rho0_g = 1.0_wp/(this%rho0*g_bt) if (present(eta_tide)) then call p_surf_update_seam_impl(nx, ny, i_rho0_g, p_surf, & this%eta_ib, this%eta_seam, & use_tide=.true., eta_tide=eta_tide) else call p_surf_update_seam_impl(nx, ny, i_rho0_g, p_surf, & this%eta_ib, this%eta_seam, & use_tide=.false.) end if end subroutine p_surf_update_seam subroutine p_surf_update_seam_impl(nx, ny, i_rho0_g, p_surf, & eta_ib, eta_seam, use_tide, eta_tide) !! Flat-impl device fill (explicit-shape dummies — no descriptor walk). !! `eta_ib(i,j) = -p_surf(i,j)*i_rho0_g` and !! `eta_seam(i,j) = eta_ib(i,j) [+ eta_tide(i,j)]`. Loop-invariant !! `use_tide` branch kept INSIDE the single `do concurrent` (one !! launch, uniform branch is ~free). Contiguous index (i) innermost. integer, intent(in) :: nx, ny real(wp), intent(in) :: i_rho0_g real(wp), intent(in) :: p_surf(nx, ny) real(wp), intent(out) :: eta_ib(nx, ny) real(wp), intent(out) :: eta_seam(nx, ny) logical, intent(in) :: use_tide real(wp), intent(in), optional :: eta_tide(nx, ny) integer :: i, j do concurrent(j=1:ny, i=1:nx) eta_ib(i, j) = -p_surf(i, j)*i_rho0_g if (use_tide) then eta_seam(i, j) = eta_ib(i, j) + eta_tide(i, j) else eta_seam(i, j) = eta_ib(i, j) end if end do end subroutine p_surf_update_seam_impl subroutine ocean_p_surf_enter_data(this) !! Attach the device-resident seam arrays. Only when enabled. !! select-type -> non-poly `_impl` (AMD libomptarget class-box rule). class(ocean_p_surf_t), intent(inout) :: this if (.not. this%enable) return select type (this) type is (ocean_p_surf_t) call ocean_p_surf_enter_data_impl(this) end select end subroutine ocean_p_surf_enter_data subroutine ocean_p_surf_enter_data_impl(this) type(ocean_p_surf_t), intent(inout) :: this if (allocated(this%eta_ib)) then !$acc enter data copyin(this%eta_ib, this%eta_seam) end if end subroutine ocean_p_surf_enter_data_impl subroutine ocean_p_surf_exit_data(this) class(ocean_p_surf_t), intent(inout) :: this if (.not. this%enable) return select type (this) type is (ocean_p_surf_t) call ocean_p_surf_exit_data_impl(this) end select end subroutine ocean_p_surf_exit_data subroutine ocean_p_surf_exit_data_impl(this) type(ocean_p_surf_t), intent(inout) :: this if (allocated(this%eta_ib)) then !$acc exit data delete(this%eta_seam, this%eta_ib) end if end subroutine ocean_p_surf_exit_data_impl pure function ocean_p_surf_bytes(this) result(nbytes) !! Counted allocatable footprint of the p_surf slot (0 when !! unallocated). One arr_bytes term per array — add a term here when !! a new allocatable joins the type. class(ocean_p_surf_t), intent(in) :: this integer(int64) :: nbytes nbytes = arr_bytes(this%eta_ib) & + arr_bytes(this%eta_seam) end function ocean_p_surf_bytes end module rdb_ocean_p_surf