rdb_ocean_porous Module

Adcroft (2013) three-parameter porous-barrier fit for the ocean C-grid dyn-core.

A model face is a straight segment of length dy_cu (u-face) or dx_cv (v-face), but the real seafloor along that segment is not flat: a strait or a sill leaves only PART of the segment open at a given depth. Porous barriers replace the binary open/closed face with a depth-dependent OPEN FRACTION, so a deep sill blocks the bottom layers while the surface layers stay fully open. The face stays a single C-grid degree of freedom — only the transport width narrows.

The along-face seafloor is summarised by three numbers per face, all TOPOGRAPHIC HEIGHTS (positive up, negative below the sea surface) so they compare directly against interface heights. NOTE the ocean path’s barotropic%b is the opposite sign — a reference column DEPTH, positive down (SSH = sum(h_layer) - b) — so the caller negates it once when filling metrics%por_bed: * d_min — deepest along-face point (most negative), * d_max — shallowest along-face point (least negative), * d_avg — mean along-face height, d_min <= d_avg <= d_max.

Adcroft’s fit picks the one-parameter family of monotone profiles that reproduces exactly those three numbers. With m = (d_avg - d_min) / (d_max - d_min) (nondim, in [0,1]) zeta = (eta - d_min) / (d_max - d_min) (nondim) a = (1 - m) / m the OPEN WIDTH FRACTION at an interface of height eta is w(eta) = 0 for eta <= d_min, w(eta) = zeta**(1/a) for m < 1/2, w(eta) = zeta for m = 1/2, w(eta) = 1 - (1 - zeta)**a for m > 1/2, w(eta) = 1 for eta > d_max, and its vertical integral from the bottom (units of length) is A(eta) = 0 eta <= d_min, A(eta) = (d_max-d_min) * (1-m) * zeta**(1/(1-m)) m < 1/2, A(eta) = (d_max-d_min) * 0.5*zeta*zeta m = 1/2, A(eta) = (d_max-d_min) * (zeta - m + m*(1-zeta)**(1/m)) m > 1/2, A(eta) = eta - d_avg eta > d_max. dA/d(eta) = w(eta) identically, and both branches join continuously at eta = d_max where A = (d_max-d_min)*(1-m).

The LAYER-AVERAGED open AREA fraction of layer k, whose lower and upper interfaces sit at face heights eta_lo / eta_hi, is the mean of w over the layer, obtained exactly from the cumulative integral: por_face_area(k) = min(1, (A(eta_hi) - A(eta_lo))/(eta_hi - eta_lo)) with a zero fallback for a vanishing layer. It multiplies the face width in the transport: uh = u * h_face * dy_cu * por_face_area_u.

Reference: Adcroft, A. (2013), “Representation of topography by porous barriers and objective interpolation of topographic data”, Ocean Modelling 67, 13-27. MOM6’s MOM_porous_barriers was the inspiration for the discrete layer-averaging and the eta-at-velocity interpolation options; this is an independent implementation re-derived from the paper’s fit.

SUBGRID DATA CAVEAT. The fit needs min/max/mean of the HIGH-RESOLUTION seafloor along each face — a statistic that can only come from a bathymetry dataset finer than the model grid (MOM6 reads it from an offline-generated topog_edge.nc). Roundabout has no such file plumbing yet, so the shipped source is POROUS_SOURCE_RESOLVED: min/max/mean of the RESOLVED bathymetry sampled at three along-face points (south corner, midpoint, north corner for a u-face). That is a genuine along-face statistic of the data we have, NOT true subgrid information — it captures along-face slope but cannot see structure below the grid scale. POROUS_SOURCE_FILE is the honest MOM6-parity path and fails loud pending the file-forcing backend (the same gate the barotropic wave-drag form="file" waits on).

WHAT THE PROXY ACTUALLY DOES — read this before trusting it. All three samples are CELL-CENTRE AVERAGES, so the statistic only sees variation ALONG the face. * Bathymetry uniform ALONG the face — a ridge or shelf break that runs parallel to it, the common case — collapses all three samples onto one value. The fit would then be a STEP at the two-cell mean height: a hard wall on every layer below it, on a face the grid already resolves as open. That wall is manufactured by the fill routine, not information, so porous_update_face_areas treats a DEGENERATE face (d_max <= d_min) as FULLY OPEN and blocks nothing. A flat seafloor is the same degenerate case, which is also what makes the resolved source a literal no-op on a flat basin. * Where the bathymetry DOES vary along the face, the narrowing is set by the spread of the three corner-mean samples. A face whose along-face spread is small compared with the water depth still yields a near-step over that narrow spread — the fit is only ever as smooth as the sampled spread. Genuinely graded narrowing needs real subgrid data (source="file").

WET-CELL GATING. A corner sample averages four cells; if any of them is LAND its elevation would pull d_max up and manufacture blockage on a face the grid resolves as fully open. porous_fill_stats_resolved therefore drops a corner sample whose four-cell stencil is not entirely wet, falling back to the two-cell face midpoint for that sample.

VANISHING-LAYER THRESHOLD. A layer whose face thickness is at or below H_VANISHED (1.5e-4 m) is given por = 0. MOM6 instead uses Angstrom_Z (1e-10 m), 1.5 million times smaller, so a layer between the two thresholds gets a real open fraction there and zero here. The consequence is that por_col (the column-integrated fraction the barotropic width carries) is the thickness-weighted mean of the per-layer fractions only over the NON-vanished layers. H_VANISHED is the repo-wide dynamic-vanish constant (rdb_constants), not a porous-barrier choice.

NOT NARROWED. areaCu / areaCv keep their full geometric value: only the TRANSPORT widths are narrowed (dy_cu / dx_cv per layer and dy_cu_bt / dx_cv_bt for the barotropic mode). The barotropic Coriolis and KE terms therefore combine a narrowed transport with an un-narrowed cell area — the same split MOM6 has, but worth knowing before reading a BT energy budget under a strong barrier.


Uses

  • module~~rdb_ocean_porous~~UsesGraph module~rdb_ocean_porous rdb_ocean_porous ieee_arithmetic ieee_arithmetic module~rdb_ocean_porous->ieee_arithmetic module~rdb_constants rdb_constants module~rdb_ocean_porous->module~rdb_constants pic_types pic_types module~rdb_constants->pic_types

Used by

  • module~~rdb_ocean_porous~~UsedByGraph module~rdb_ocean_porous rdb_ocean_porous module~rdb_coriolis_adv rdb_coriolis_adv module~rdb_coriolis_adv->module~rdb_ocean_porous module~rdb_ocean_dyn rdb_ocean_dyn module~rdb_ocean_dyn->module~rdb_ocean_porous module~rdb_ocean_dyn->module~rdb_coriolis_adv module~rdb_barotropic_coupling rdb_barotropic_coupling module~rdb_ocean_dyn->module~rdb_barotropic_coupling module~rdb_ocean_bt_budget_probe rdb_ocean_bt_budget_probe module~rdb_ocean_dyn->module~rdb_ocean_bt_budget_probe module~rdb_ocean_ke_probe rdb_ocean_ke_probe module~rdb_ocean_dyn->module~rdb_ocean_ke_probe module~rdb_ocean_setup rdb_ocean_setup module~rdb_ocean_setup->module~rdb_ocean_porous module~rdb_ocean_setup->module~rdb_coriolis_adv module~rdb_ocean_setup->module~rdb_ocean_dyn module~rdb_ocean_state rdb_ocean_state module~rdb_ocean_setup->module~rdb_ocean_state module~rdb_barotropic_coupling->module~rdb_coriolis_adv module~rdb_driver rdb_driver module~rdb_driver->module~rdb_ocean_dyn module~rdb_ocean_engine rdb_ocean_engine module~rdb_driver->module~rdb_ocean_engine module~rdb_driver->module~rdb_ocean_state module~rdb_ocean_api rdb_ocean_api module~rdb_ocean_api->module~rdb_ocean_dyn module~rdb_ocean_api->module~rdb_ocean_engine module~rdb_ocean_halo_width rdb_ocean_halo_width module~rdb_ocean_api->module~rdb_ocean_halo_width module~rdb_handle rdb_handle module~rdb_ocean_api->module~rdb_handle module~rdb_ocean_diag_derived rdb_ocean_diag_derived module~rdb_ocean_api->module~rdb_ocean_diag_derived module~rdb_ocean_diag_fills rdb_ocean_diag_fills module~rdb_ocean_api->module~rdb_ocean_diag_fills module~rdb_ocean_bt_budget_probe->module~rdb_coriolis_adv module~rdb_ocean_engine->module~rdb_ocean_dyn module~rdb_ocean_engine->module~rdb_ocean_setup module~rdb_ocean_engine->module~rdb_ocean_state module~rdb_ocean_engine->module~rdb_ocean_diag_derived module~rdb_ocean_engine->module~rdb_ocean_diag_fills module~rdb_ocean_halo_width->module~rdb_coriolis_adv module~rdb_ocean_ke_probe->module~rdb_coriolis_adv module~rdb_ocean_state->module~rdb_coriolis_adv module~rdb_ocean_state->module~rdb_ocean_dyn proc~validate_config validate_config proc~validate_config->module~rdb_coriolis_adv module~rdb_handle->module~rdb_ocean_engine module~rdb_handle->module~rdb_ocean_state module~rdb_ocean_diag_derived->module~rdb_ocean_state module~rdb_ocean_diag_derived->module~rdb_ocean_diag_fills module~rdb_ocean_diag_fills->module~rdb_ocean_state

Variables

Type Visibility Attributes Name Initial
integer, public, parameter :: POROUS_ETA_ARITH = 2

Arithmetic mean of the two adjacent interface heights.

integer, public, parameter :: POROUS_ETA_HARM = 3

Harmonic mean of the two adjacent interface heights.

integer, public, parameter :: POROUS_ETA_MAX = 0

Higher (shallower) of the two adjacent interface heights — the default, and the LEAST blocking of the four rules: w is monotone increasing in the interface height, so raising both interfaces raises the layer-averaged open fraction.

integer, public, parameter :: POROUS_ETA_MIN = 1

Lower (deeper) of the two adjacent interface heights — the MOST blocking rule, by the same monotonicity.

integer, public, parameter :: POROUS_SOURCE_FILE = 1

Offline subgrid-bathymetry file (MOM6 topog_edge.nc). Not implemented — fails loud at configure.

integer, public, parameter :: POROUS_SOURCE_RESOLVED = 0

Along-face min/max/mean of the RESOLVED bathymetry (three samples: the two face corners + the face midpoint). A proxy for true subgrid statistics — see the module caveat.


Functions

public pure function parse_porous_eta_interp(name) result(interp)

Map &ocean_porous_nml eta_interp onto POROUS_ETA_*. An unrecognised string returns -1 so the caller can fail loud.

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: name

Return Value integer

public pure function parse_porous_source(name) result(src)

Map &ocean_porous_nml source onto POROUS_SOURCE_*. An unrecognised string returns -1 so the caller can fail loud.

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: name

Return Value integer

public pure function porous_cum_area(d_min, d_max, d_avg, eta) result(area)

Cumulative open area of a face from the deepest along-face point up to interface height eta, per unit face length (m — it is the vertical integral of porous_open_width). Layer-averaged open fractions are differences of this function divided by the layer thickness, which is exact (no quadrature error) because d(area)/d(eta) = porous_open_width(eta) identically.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: d_min

Deepest along-face topographic height (m, positive up).

real(kind=wp), intent(in) :: d_max

Shallowest along-face topographic height (m, positive up).

real(kind=wp), intent(in) :: d_avg

Mean along-face topographic height (m, positive up).

real(kind=wp), intent(in) :: eta

Interface height at the face (m, positive up).

Return Value real(kind=wp)

public pure function porous_eta_face(z_a, z_b, interp) result(eta)

Interface height at a velocity point from the two adjacent cell-centre interface heights. MOM6’s PORBAR_ETA_INTERP options. POROUS_ETA_MAX (the higher, i.e. shallower, interface) is the default and the LEAST blocking: the open width w is monotone increasing in the interface height, so the rule that returns the larger height leaves the most of the face open. POROUS_ETA_MIN is the most blocking.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: z_a

Interface height in the first adjacent cell (m, positive up).

real(kind=wp), intent(in) :: z_b

Interface height in the second adjacent cell (m, positive up).

integer, intent(in) :: interp

One of the POROUS_ETA_* enum values.

Return Value real(kind=wp)

public pure function porous_open_width(d_min, d_max, d_avg, eta) result(w)

Open WIDTH fraction of a face at interface height eta (dimensionless, in [0, 1]). Zero when the interface is at or below the deepest along-face point, one when it is above the shallowest. A degenerate face (d_max <= d_min, i.e. a flat along-face seafloor) reduces to the binary open/closed step, so the fit never divides by zero.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: d_min

Deepest along-face topographic height (m, positive up).

real(kind=wp), intent(in) :: d_max

Shallowest along-face topographic height (m, positive up).

real(kind=wp), intent(in) :: d_avg

Mean along-face topographic height (m, positive up).

real(kind=wp), intent(in) :: eta

Interface height at the face (m, positive up).

Return Value real(kind=wp)

public pure function porous_stats_are_ordered(n1, n2, dmin, dmax, davg) result(ok)

.true. iff every face satisfies d_min <= d_avg <= d_max, the invariant the whole fit rests on (m = (d_avg-d_min)/(d_max-d_min) must lie in [0,1]).

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: n1

Face-array extents.

integer, intent(in) :: n2

Face-array extents.

real(kind=wp), intent(in) :: dmin(n1,n2)

Along-face deepest / shallowest / mean height (m, positive up).

real(kind=wp), intent(in) :: dmax(n1,n2)

Along-face deepest / shallowest / mean height (m, positive up).

real(kind=wp), intent(in) :: davg(n1,n2)

Along-face deepest / shallowest / mean height (m, positive up).

Return Value logical

private pure function clamp_fraction(x) result(f)

Clamp an open-area fraction into [0,1], NaN-safely.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x

Return Value real(kind=wp)

private pure function corner_is_wet(w1, w2, w3, w4) result(ok)

.true. iff all four cells contributing to a corner sample are wet. The masks are real 0/1 (ocean_metrics_t%wet_T), so the test is a mid-point comparison rather than an equality.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: w1
real(kind=wp), intent(in) :: w2
real(kind=wp), intent(in) :: w3
real(kind=wp), intent(in) :: w4

Return Value logical


Subroutines

public pure subroutine closed_faces_update_bt_widths(nx, ny, nz, use_por, dy_cu, dx_cv, h_layer, por_u, por_v, open_u, open_v, dy_cu_bt, dx_cv_bt)

Refresh the BAROTROPIC face widths from the LIVE layer thicknesses when &vcoord_nml zfixed_closed_faces is on.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
logical, intent(in) :: use_por

Porous barriers also active. .false. => por_u/por_v are never indexed, and the caller must hand over a full-size, device-present stand-in rather than the (1,1,1) porous placeholder: nvfortran builds the do concurrent data clause from the LOOP BOUNDS, not from the descriptor, so a placeholder aborts under mem:separate (“variable in data clause is partially present”) even though the branch that indexes it is never taken. ocean_porous_refresh passes open_u/open_v themselves — right shape, already mapped, intent(in) on both dummies so the double association is not aliasing. (Found by the GPU build; both CPU builds were silently happy.)

real(kind=wp), intent(in) :: dy_cu(nx+1,ny)
real(kind=wp), intent(in) :: dx_cv(nx,ny+1)
real(kind=wp), intent(in) :: h_layer(nx,ny,nz)
real(kind=wp), intent(in) :: por_u(nx+1,ny,nz)
real(kind=wp), intent(in) :: por_v(nx,ny+1,nz)
real(kind=wp), intent(in) :: open_u(nx+1,ny,nz)
real(kind=wp), intent(in) :: open_v(nx,ny+1,nz)
real(kind=wp), intent(inout) :: dy_cu_bt(nx+1,ny)
real(kind=wp), intent(inout) :: dx_cv_bt(nx,ny+1)

public pure subroutine porous_fill_stats_resolved(nx, ny, b, wet_t, dmin_u, dmax_u, davg_u, dmin_v, dmax_v, davg_v)

Fill the per-face d_min / d_max / d_avg from the RESOLVED bottom elevation, sampled at three points ALONG each face: the two end corners and the midpoint. A corner sample is the mean of the four cells around it, the midpoint the mean of the two cells the face separates — all three are genuine points on the resolved seafloor along the face segment.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx

grid%nx_total, grid%ny_total.

integer, intent(in) :: ny

grid%nx_total, grid%ny_total.

real(kind=wp), intent(in) :: b(nx,ny)

Bottom elevation at cell centres (m, positive up).

real(kind=wp), intent(in) :: wet_t(nx,ny)

T-cell wet (1) / land (0) mask (ocean_metrics_t%wet_T). All-ones on a run with no land, where every corner sample is kept and the statistic is the un-gated one.

real(kind=wp), intent(out) :: dmin_u(nx+1,ny)

u-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(out) :: dmax_u(nx+1,ny)

u-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(out) :: davg_u(nx+1,ny)

u-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(out) :: dmin_v(nx,ny+1)

v-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(out) :: dmax_v(nx,ny+1)

v-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(out) :: davg_v(nx,ny+1)

v-face along-face deepest / shallowest / mean height (m).

public pure subroutine porous_narrow_3d(n1, n2, nz, por, arr)

Multiply a face-staggered per-layer field by the open-area fraction: arr <- arr * por.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: n1

Face-array extents (nx+1, ny, nz for u; nx, ny+1, nz for v).

integer, intent(in) :: n2

Face-array extents (nx+1, ny, nz for u; nx, ny+1, nz for v).

integer, intent(in) :: nz

Face-array extents (nx+1, ny, nz for u; nx, ny+1, nz for v).

real(kind=wp), intent(in) :: por(n1,n2,nz)

Layer-averaged open-area fraction (nondim, [0,1]).

real(kind=wp), intent(inout) :: arr(n1,n2,nz)

Face-staggered field to narrow (a mass transport).

public pure subroutine porous_update_face_areas(nx, ny, nz, interp, mask_depth, b, h_layer, dmin_u, dmax_u, davg_u, dmin_v, dmax_v, davg_v, dy_cu, dx_cv, por_u, por_v, dy_cu_bt, dx_cv_bt)

Recompute the layer-averaged open-area fractions from the CURRENT layer thicknesses. Interface-height dependent, so this runs once per RK2 stage on the device.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx

grid%nx_total, grid%ny_total, number of layers.

integer, intent(in) :: ny

grid%nx_total, grid%ny_total, number of layers.

integer, intent(in) :: nz

grid%nx_total, grid%ny_total, number of layers.

integer, intent(in) :: interp

Interface-at-velocity-point rule, a POROUS_ETA_* value.

real(kind=wp), intent(in) :: mask_depth

Gate height (m, positive up, <= 0): faces with d_avg >= mask_depth stay fully open.

real(kind=wp), intent(in) :: b(nx,ny)

Bottom elevation at cell centres (m, positive up).

real(kind=wp), intent(in) :: h_layer(nx,ny,nz)

Layer thicknesses (m), k=1 the bed layer.

real(kind=wp), intent(in) :: dmin_u(nx+1,ny)

u-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(in) :: dmax_u(nx+1,ny)

u-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(in) :: davg_u(nx+1,ny)

u-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(in) :: dmin_v(nx,ny+1)

v-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(in) :: dmax_v(nx,ny+1)

v-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(in) :: davg_v(nx,ny+1)

v-face along-face deepest / shallowest / mean height (m).

real(kind=wp), intent(in) :: dy_cu(nx+1,ny)

Un-narrowed open u-face width for transport (m).

real(kind=wp), intent(in) :: dx_cv(nx,ny+1)

Un-narrowed open v-face width for transport (m).

real(kind=wp), intent(out) :: por_u(nx+1,ny,nz)

u-face layer-averaged open-area fraction (nondim, [0,1]).

real(kind=wp), intent(out) :: por_v(nx,ny+1,nz)

v-face layer-averaged open-area fraction (nondim, [0,1]).

real(kind=wp), intent(out) :: dy_cu_bt(nx+1,ny)

u-face width the BAROTROPIC substep transports on (m): dy_cu scaled by the COLUMN-INTEGRATED open fraction.

real(kind=wp), intent(out) :: dx_cv_bt(nx,ny+1)

v-face twin (m).