ocean_surface_stress_t Derived Type

type, public :: ocean_surface_stress_t


Inherits

type~~ocean_surface_stress_t~~InheritsGraph type~ocean_surface_stress_t ocean_surface_stress_t type~scratch_3d_buffer_t scratch_3d_buffer_t type~ocean_surface_stress_t->type~scratch_3d_buffer_t du_stress, dv_stress

Inherited by

type~~ocean_surface_stress_t~~InheritedByGraph type~ocean_surface_stress_t ocean_surface_stress_t type~ocean_state_t ocean_state_t type~ocean_state_t->type~ocean_surface_stress_t surface_stress type~ocean_engine_t ocean_engine_t type~ocean_engine_t->type~ocean_state_t state type~ocean_handle_t ocean_handle_t type~ocean_handle_t->type~ocean_state_t state type~ocean_handle_t->type~ocean_engine_t engine

Components

Type Visibility Attributes Name Initial
logical, public :: direct_stress = .false.

MOM6 DIRECT_STRESS analogue. When .true. the wind stress is distributed across the top hmix_stress metres rather than concentrated in the surface-most layer. Mirrors HYCOM’s approach: stress acts on a surface slab of fixed thickness, with each layer in that slab getting a proportional share. Has no effect when the top layer is already thinner than hmix_stress (bit-identical to the bed-only branch in that limit).

type(scratch_3d_buffer_t), public :: du_stress

Surface stress tendency at east faces, shape (nx+1, ny, nz). Only k=nz carries a non-zero value; k

type(scratch_3d_buffer_t), public :: dv_stress

Surface stress tendency at north faces, shape (nx, ny+1, nz).

real(kind=wp), public :: h_min = 1.0e-3_wp

Floor on the surface-layer thickness in the 1/h_top division — keeps the kernel finite when the top layer pinches out (rare; e.g., wave breaking under ZSTAR_FULL).

logical, public :: has_stress_mag = .false.

True once ocean_surface_stress_set_derived has filled stress_mag at least once. Informational — no kernel gates on it (stress_mag is always allocated and zero-safe).

real(kind=wp), public :: hmix_stress = 0.0_wp

Thickness (m) of the surface slab over which the stress is spread when direct_stress = .true.. MOM6 production default 20 m. Zero (default) keeps the surface-layer-only behaviour even if direct_stress is flipped on.

logical, public :: is_init = .false.

True between init and destroy.

real(kind=wp), public :: rho0 = 1035.0_wp

Boussinesq reference density (kg/m^3) used in the tau / (rho_0 * h) acceleration (top-layer and DIRECT_STRESS distributed forms alike).

ASSIGNED FROM CONFIG by configure_ocean_reference_density, which copies the single rho0 of record (&ocean_ic_nml rho_0 -> eos%rho0). The literal here is only the pre-configure type default. Host scalar: passed by value into the surfstress_*_impl kernels, so no !$acc update device.

real(kind=wp), public, allocatable :: stress_mag(:,:)

Cell-centred wind-stress magnitude |tau| (Pa, sqrt(tau_x_cell^2 + tau_y_cell^2)), shape (nx_total, ny_total). Always allocated (PR-12 §7.4: a pure τ property, unlike a shared ustar which would need a coherent rho0 — deferred). Refreshed in step by every writer of the tau pair: the set_wind_stress_* setters and ocean_surface_stress_set_derived at configure, the data-forcing reader’s seam refresh per bracket, and the sea-ice stress coupler’s on-device blend every outer step (all via ocean_surface_stress_refresh_mag). KPP (rdb_ocean_vmix) and EPBL (rdb_ocean_epbl) both read it instead of re-deriving tau_mag inline (bit-identical dedup — same three lines, same FP op order, just computed once) — which is why a tau write that skips the refresh silently freezes BOTH schemes’ u_* at the last refreshed stress.

Under an ice-shelf cavity the refresh is not enough on its own: a tau writer must ALSO re-apply the cover mask, which is why ocean_surface_stress_apply_cover does both in one call (see the module docstring’s cover contract). What the mask leaves behind under the ice — the ICE-OCEAN stress — is stress_shelf below, deliberately not folded in here.

real(kind=wp), public, allocatable :: stress_shelf(:,:)

Cell-centred magnitude of the stress an ICE-SHELF BASE exerts on the ocean (N/m^2, >= 0), shape (nx_total, ny_total), valid including ghosts. Always allocated, mapped and counted; exactly zero without a cavity, and exactly zero on every cell with cover_frac = 0.

NOT derived from tau — the ice-ocean stress is a separate momentum tendency (rdb_ocean_top_drag), so this field is NOT touched by ocean_surfstress_refresh_stress_mag and NOT touched by ocean_surface_stress_apply_cover. It is refreshed by the split/unsplit RK2 drivers, inline, in the same stage that recomputes the top drag and strictly before vmix_apply_in_stage reads it (no lag), from ocean_top_drag_t%stress_top. When the top drag is OFF but &ocean_cavity_melt_nml enable is on, engine_step_finalize fills it instead from rho_0*u_*^2 with the melt slot’s own u_* — the SAME C_d under the one-drag-coefficient rule, but at the thermo cadence, so THAT path is lagged one outer step. Both are documented in the module docstring.

Consumers: KPP (rdb_ocean_vmix) and EPBL (rdb_ocean_epbl), both as u_* = sqrt((stress_mag + stress_shelf)/rho_0).

real(kind=wp), public, allocatable :: tau_x(:,:)

Zonal wind stress (N/m^2) on east faces, shape (nx+1, ny). Fill via set_wind_stress_const for spatially-uniform wind, or assign the array directly for spatially-varying input. Default zero.

real(kind=wp), public, allocatable :: tau_y(:,:)

Meridional wind stress (N/m^2) on north faces, shape (nx, ny+1).


Type-Bound Procedures

procedure, public, non_overridable :: bytes => ocean_surface_stress_bytes

  • private pure function ocean_surface_stress_bytes(this) result(nbytes)

    Counted allocatable footprint of the surface stress slot (0 when unallocated). One arr_bytes term per array — add a term here when a new allocatable joins the type.

    Arguments

    Type IntentOptional Attributes Name
    class(ocean_surface_stress_t), intent(in) :: this

    Return Value integer(kind=int64)

procedure, public, non_overridable :: destroy => ocean_surfstress_destroy

procedure, public, non_overridable :: enter_data => ocean_surfstress_enter_data

  • private subroutine ocean_surfstress_enter_data(this)

    Type-bound wrapper — delegates to the non-polymorphic impl so the device-attach map base is the heap object, not a polymorphic stack box (AMD libomptarget cross-slot-overlap fix). Slot-header presence comes from the orchestrator’s root copyin(state); only the leaf arrays + scratch are attached here.

    Arguments

    Type IntentOptional Attributes Name
    class(ocean_surface_stress_t), intent(inout) :: this

procedure, public, non_overridable :: exit_data => ocean_surfstress_exit_data

procedure, public, non_overridable :: init => ocean_surfstress_init

procedure, public, non_overridable :: set_wind_stress_2gyre => ocean_surfstress_set_2gyre

  • private subroutine ocean_surfstress_set_2gyre(this, grid, taux_mag, j_offset, ny_global)

    Fill tau_x with the MOM6 2gyre profile, tau_x(i,j) = taux_mag · (1 − cos(2π · (y − y_south) / y_len)), and zero tau_y. In Cartesian terms (y − y_south) / y_len is the normalised position from the south wall of the physical domain (0 at south, 1 at north), so the formula reduces to taux_mag · (1 − cos(2π · ((j_phys − 0.5) / ny_phys))) with j_phys = j − nghost. Physical-interior rows only; ghost rows stay at zero so wall faces see no spurious stress. Host only — call enter_data afterwards (or !$acc update device if already mapped).

    Read more…

    Arguments

    Type IntentOptional Attributes Name
    class(ocean_surface_stress_t), intent(inout) :: this
    type(hgrid_t), intent(in) :: grid
    real(kind=wp), intent(in) :: taux_mag
    integer, intent(in), optional :: j_offset
    integer, intent(in), optional :: ny_global

procedure, public, non_overridable :: set_wind_stress_const => ocean_surfstress_set_const

  • private subroutine ocean_surfstress_set_const(this, tau_x_val, tau_y_val)

    Fill tau_x / tau_y uniformly with the given scalar values. Convenience helper for the spatially-constant wind case (the Tier-1 default and most unit tests). Host only — call enter_data afterwards (or !$acc update device if already mapped) to sync to GPU. Refreshes stress_mag in step (PR-12) so every existing caller — production and unit test alike — gets a consistent stress_mag with no separate call required.

    Arguments

    Type IntentOptional Attributes Name
    class(ocean_surface_stress_t), intent(inout) :: this
    real(kind=wp), intent(in) :: tau_x_val
    real(kind=wp), intent(in) :: tau_y_val

procedure, public, non_overridable :: set_wind_stress_neverworld2 => ocean_surfstress_set_neverworld2

  • private subroutine ocean_surfstress_set_neverworld2(this, grid, taux_mag, j_offset, ny_global)

    Fill tau_x with the Neverworld2 zonal wind-stress profile (Marques et al. 2022, GMD; MOM6-inspired) and zero tau_y. τ_x is a 3-band piecewise function of the normalized meridional position y = (j_phys − 0.5)/ny_phys ∈ [0,1] (which equals MOM6’s (lat − south)/len_lat on a uniform grid), scaled by the peak stress taux_mag (Pa), with off = 0.02:

    Read more…

    Arguments

    Type IntentOptional Attributes Name
    class(ocean_surface_stress_t), intent(inout) :: this
    type(hgrid_t), intent(in) :: grid
    real(kind=wp), intent(in) :: taux_mag
    integer, intent(in), optional :: j_offset
    integer, intent(in), optional :: ny_global

Source Code

   type :: ocean_surface_stress_t
      logical :: is_init = .false.
         !! True between `init` and `destroy`.
      real(wp) :: rho0 = 1035.0_wp
         !! Boussinesq reference density (kg/m^3) used in the
         !! `tau / (rho_0 * h)` acceleration (top-layer and DIRECT_STRESS
         !! distributed forms alike).
         !!
         !! ASSIGNED FROM CONFIG by `configure_ocean_reference_density`,
         !! which copies the single rho0 of record (`&ocean_ic_nml rho_0`
         !! -> `eos%rho0`).  The literal here is only the pre-configure
         !! type default.  Host scalar: passed by value into the
         !! `surfstress_*_impl` kernels, so no `!$acc update device`.
      real(wp) :: h_min = 1.0e-3_wp
         !! Floor on the surface-layer thickness in the `1/h_top`
         !! division — keeps the kernel finite when the top layer
         !! pinches out (rare; e.g., wave breaking under ZSTAR_FULL).
      logical :: direct_stress = .false.
         !! MOM6 DIRECT_STRESS analogue.  When `.true.` the wind
         !! stress is distributed across the top `hmix_stress` metres
         !! rather than concentrated in the surface-most layer.
         !! Mirrors HYCOM's approach: stress acts on a surface slab
         !! of fixed thickness, with each layer in that slab getting
         !! a proportional share.  Has no effect when the top layer
         !! is already thinner than `hmix_stress` (bit-identical to
         !! the bed-only branch in that limit).
      real(wp) :: hmix_stress = 0.0_wp
         !! Thickness (m) of the surface slab over which the stress
         !! is spread when `direct_stress = .true.`.  MOM6 production
         !! default 20 m.  Zero (default) keeps the surface-layer-only
         !! behaviour even if `direct_stress` is flipped on.

      real(wp), allocatable :: tau_x(:, :)
         !! Zonal wind stress (N/m^2) on east faces, shape `(nx+1, ny)`.
         !! Fill via `set_wind_stress_const` for spatially-uniform
         !! wind, or assign the array directly for spatially-varying
         !! input.  Default zero.
      real(wp), allocatable :: tau_y(:, :)
         !! Meridional wind stress (N/m^2) on north faces, shape
         !! `(nx, ny+1)`.
      logical :: has_stress_mag = .false.
         !! True once `ocean_surface_stress_set_derived` has filled
         !! `stress_mag` at least once.  Informational — no kernel gates
         !! on it (`stress_mag` is always allocated and zero-safe).
      real(wp), allocatable :: stress_mag(:, :)
         !! Cell-centred wind-stress magnitude `|tau|` (Pa,
         !! `sqrt(tau_x_cell^2 + tau_y_cell^2)`), shape `(nx_total,
         !! ny_total)`.  **Always allocated** (PR-12 §7.4: a pure τ
         !! property, unlike a shared `ustar` which would need a
         !! coherent `rho0` — deferred).  Refreshed in step by every
         !! writer of the `tau` pair: the `set_wind_stress_*` setters and
         !! `ocean_surface_stress_set_derived` at configure, the
         !! data-forcing reader's seam refresh per bracket, and the
         !! sea-ice stress coupler's on-device blend every outer step
         !! (all via `ocean_surface_stress_refresh_mag`).  KPP
         !! (`rdb_ocean_vmix`)
         !! and EPBL (`rdb_ocean_epbl`) both read it instead of
         !! re-deriving `tau_mag` inline (bit-identical dedup — same
         !! three lines, same FP op order, just computed once) — which is
         !! why a `tau` write that skips the refresh silently freezes
         !! BOTH schemes' `u_*` at the last refreshed stress.
         !!
         !! Under an ice-shelf cavity the refresh is not enough on its
         !! own: a `tau` writer must ALSO re-apply the cover mask, which
         !! is why `ocean_surface_stress_apply_cover` does both in one
         !! call (see the module docstring's cover contract).  What the
         !! mask leaves behind under the ice — the ICE-OCEAN stress — is
         !! `stress_shelf` below, deliberately not folded in here.
      real(wp), allocatable :: stress_shelf(:, :)
         !! Cell-centred magnitude of the stress an ICE-SHELF BASE
         !! exerts on the ocean (N/m^2, `>= 0`), shape `(nx_total,
         !! ny_total)`, valid including ghosts.  **Always allocated**,
         !! mapped and counted; exactly zero without a cavity, and
         !! exactly zero on every cell with `cover_frac = 0`.
         !!
         !! NOT derived from `tau` — the ice-ocean stress is a separate
         !! momentum tendency (`rdb_ocean_top_drag`), so this field is
         !! NOT touched by `ocean_surfstress_refresh_stress_mag` and NOT
         !! touched by `ocean_surface_stress_apply_cover`.  It is
         !! refreshed by the split/unsplit RK2 drivers, inline, in the
         !! same stage that recomputes the top drag and strictly before
         !! `vmix_apply_in_stage` reads it (no lag), from
         !! `ocean_top_drag_t%stress_top`.  When the top drag is OFF but
         !! `&ocean_cavity_melt_nml enable` is on, `engine_step_finalize`
         !! fills it instead from `rho_0*u_*^2` with the melt slot's own
         !! `u_*` — the SAME `C_d` under the one-drag-coefficient rule,
         !! but at the thermo cadence, so THAT path is lagged one outer
         !! step.  Both are documented in the module docstring.
         !!
         !! Consumers: KPP (`rdb_ocean_vmix`) and EPBL
         !! (`rdb_ocean_epbl`), both as
         !! `u_* = sqrt((stress_mag + stress_shelf)/rho_0)`.

      type(scratch_3d_buffer_t) :: du_stress
         !! Surface stress tendency at east faces, shape
         !! (nx+1, ny, nz).  Only k=nz carries a non-zero value;
         !! k<nz stays zero.
      type(scratch_3d_buffer_t) :: dv_stress
         !! Surface stress tendency at north faces, shape
         !! (nx, ny+1, nz).
   contains
      procedure, non_overridable :: init => ocean_surfstress_init
      procedure, non_overridable :: destroy => ocean_surfstress_destroy
      procedure, non_overridable :: enter_data => ocean_surfstress_enter_data
      procedure, non_overridable :: exit_data => ocean_surfstress_exit_data
      procedure, non_overridable :: set_wind_stress_const => ocean_surfstress_set_const
      procedure, non_overridable :: set_wind_stress_2gyre => ocean_surfstress_set_2gyre
      procedure, non_overridable :: set_wind_stress_neverworld2 => ocean_surfstress_set_neverworld2
      procedure, non_overridable :: bytes => ocean_surface_stress_bytes
   end type ocean_surface_stress_t