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.
| 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: |
| 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 |
| 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. |
Map &ocean_porous_nml eta_interp onto POROUS_ETA_*. An
unrecognised string returns -1 so the caller can fail loud.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| character(len=*), | intent(in) | :: | name |
Map &ocean_porous_nml source onto POROUS_SOURCE_*. An
unrecognised string returns -1 so the caller can fail loud.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| character(len=*), | intent(in) | :: | name |
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.
| Type | Intent | Optional | 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). |
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.
| Type | Intent | Optional | 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 |
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.
| Type | Intent | Optional | 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). |
.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]).
| Type | Intent | Optional | 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). |
Clamp an open-area fraction into [0,1], NaN-safely.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x |
.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.
| Type | Intent | Optional | 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 |
Refresh the BAROTROPIC face widths from the LIVE layer
thicknesses when &vcoord_nml zfixed_closed_faces is on.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx | |||
| integer, | intent(in) | :: | ny | |||
| integer, | intent(in) | :: | nz | |||
| logical, | intent(in) | :: | use_por |
Porous barriers also active. |
||
| 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) |
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.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx |
|
||
| integer, | intent(in) | :: | ny |
|
||
| 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 ( |
||
| 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). |
Multiply a face-staggered per-layer field by the open-area
fraction: arr <- arr * por.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | n1 |
Face-array extents ( |
||
| integer, | intent(in) | :: | n2 |
Face-array extents ( |
||
| integer, | intent(in) | :: | nz |
Face-array extents ( |
||
| real(kind=wp), | intent(in) | :: | por(n1,n2,nz) |
Layer-averaged open-area fraction (nondim, |
||
| real(kind=wp), | intent(inout) | :: | arr(n1,n2,nz) |
Face-staggered field to narrow (a mass transport). |
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.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | nx |
|
||
| integer, | intent(in) | :: | ny |
|
||
| integer, | intent(in) | :: | nz |
|
||
| integer, | intent(in) | :: | interp |
Interface-at-velocity-point rule, a |
||
| real(kind=wp), | intent(in) | :: | mask_depth |
Gate height (m, positive up, |
||
| 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), |
||
| 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, |
||
| real(kind=wp), | intent(out) | :: | por_v(nx,ny+1,nz) |
v-face layer-averaged open-area fraction (nondim, |
||
| real(kind=wp), | intent(out) | :: | dy_cu_bt(nx+1,ny) |
u-face width the BAROTROPIC substep transports on (m):
|
||
| real(kind=wp), | intent(out) | :: | dx_cv_bt(nx,ny+1) |
v-face twin (m). |