VCOORD_ZSTAR target grid — MOM6 z* (REGRIDDING_COORDINATE_MODE
= "Z*", build_zstar_column, MOM6 src/ALE/coord_zlike.F90
lines 65-146): the FIXED nominal z profile of z_fixed
(&vcoord_nml z_fixed_profile uniform / list / tanh — the same
z_fixed_zi table, not a parallel one) DILATED per column by the
free-surface stretching, over a partial bed cell and inert bed
fillers.
Without a rigid top MOM6 sets stretching = (H+η)/H
(coord_zlike.F90:109), lays the interfaces top-down from the free
surface at z_k = η − stretching·Z_k (Z_k the nominal depth,
l.130-134), pins the bottom interface at −H and clamps upward so
no layer is thinner than min_thickness (l.138-143). The height
of nominal interface k above the bed is therefore
stretching·(H − Z_k): a layer lies below the bed iff its nominal
top Z_k ≥ H, WHATEVER η is (stretching > 0). The live/filler
pattern is exactly static in η, of either sign.
Two passes per column, both the z_fixed bed walk
(ocean_vcoord_z_fixed_target with z_top = 0):
η = 0, count the bed fillers n_f — the layers whose
nominal top is at or below the bed less the partial-cell
floor (Z_FIXED_BED_PARTIAL_MIN), exactly z_fixed’s rule;H + η: layers k <= n_f at
h_min, every live interface at the DILATED nominal depth
s·Z_k below the free surface, with
s = (H + η − n_f·h_min)/(H − n_f·h_min).s is MOM6’s stretching with the filler stack taken out of the
dilation (MOM6 dilates all of H and then clamps the fillers back
to min_thickness, which leaves (s−1)·n_f·h_min in the partial
cell — under 1 mm per metre of η on the 1-degree Southern Ocean,
python_prototypes/mom6_zstar/mom6_zstar.py). Consequences:
η = 0, so the
pattern is static BY CONSTRUCTION and the static face mask
(zfixed_closed_faces) applies with every consumer unchanged;η = 0 thickness times s (to round-off) — the dilation keeps
ratios, so a partial cell (p0 > Z_FIXED_BED_PARTIAL_MIN at
η = 0 by construction) stays above H_VANISHED for every
s > H_VANISHED/p0, i.e. for every η that does not all but
dry the column;η = 0, s == 1 exactly (numerator and denominator are the
same expression), s·Z_k == Z_k, and the walk is z_fixed’s
operation for operation: the η = 0 z* target IS the η = 0
z_fixed target, bit for bit.Σ_k target_h = H + η by construction (each step assigns what it
takes from z_below, and the surface layer closes the column).
A degenerate column (H − n_f·h_min <= h_min: land, or thinner
than its filler stack) is laid with s = 1, z_fixed’s own
degenerate overshoot. s is floored at 0: a column drained past
its fillers is a dry column, which this coordinate does not
support (validate_config refuses it with wet/dry).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(out) | :: | target_h(nx,ny,nz) |
Target layer thickness (m), bottom-up. |
||
| real(kind=wp), | intent(in) | :: | total_h(nx,ny) |
Column reference thickness |
||
| real(kind=wp), | intent(in) | :: | eta(nx,ny) |
Free-surface anomaly |
||
| integer, | intent(in) | :: | nx |
i-extent of every array (total, incl. halos). |
||
| integer, | intent(in) | :: | ny |
j-extent of every array (total, incl. halos). |
||
| integer, | intent(in) | :: | nz |
Number of layers; |
||
| real(kind=wp), | intent(in) | :: | h_nominal |
Uniform nominal spacing |
||
| logical, | intent(in) | :: | use_profile |
Take the nominal interfaces from |
||
| real(kind=wp), | intent(in) | :: | zi(0:nz) |
Nominal interface depths (m), bottom-up, |
||
| real(kind=wp), | intent(in) | :: | h_min |
Inert-filler thickness ( |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private | :: | bed_partial_min | ||||
| integer, | private | :: | i | ||||
| integer, | private | :: | j | ||||
| integer, | private | :: | k | ||||
| real(kind=wp), | private | :: | l0_loc | ||||
| integer, | private | :: | n_f | ||||
| real(kind=wp), | private | :: | stretch_loc | ||||
| logical, | private | :: | walking_loc | ||||
| real(kind=wp), | private | :: | z_above_loc | ||||
| real(kind=wp), | private | :: | z_below_loc | ||||
| real(kind=wp), | private | :: | z_nom_loc |
pure subroutine ocean_vcoord_zstar_target(target_h, total_h, eta, nx, ny, nz, & h_nominal, use_profile, zi, h_min) !! `VCOORD_ZSTAR` target grid — MOM6 z* (`REGRIDDING_COORDINATE_MODE !! = "Z*"`, `build_zstar_column`, MOM6 `src/ALE/coord_zlike.F90` !! lines 65-146): the FIXED nominal z profile of `z_fixed` !! (`&vcoord_nml z_fixed_profile` uniform / list / tanh — the same !! `z_fixed_zi` table, not a parallel one) DILATED per column by the !! free-surface stretching, over a partial bed cell and inert bed !! fillers. !! !! ### MOM6 !! !! Without a rigid top MOM6 sets `stretching = (H+η)/H` !! (coord_zlike.F90:109), lays the interfaces top-down from the free !! surface at `z_k = η − stretching·Z_k` (`Z_k` the nominal depth, !! l.130-134), pins the bottom interface at `−H` and clamps upward so !! no layer is thinner than `min_thickness` (l.138-143). The height !! of nominal interface `k` above the bed is therefore !! `stretching·(H − Z_k)`: a layer lies below the bed iff its nominal !! top `Z_k ≥ H`, WHATEVER `η` is (`stretching > 0`). The live/filler !! pattern is exactly static in `η`, of either sign. !! !! ### This kernel !! !! Two passes per column, both the `z_fixed` bed walk !! (`ocean_vcoord_z_fixed_target` with `z_top = 0`): !! !! 1. at `η = 0`, count the bed fillers `n_f` — the layers whose !! nominal top is at or below the bed less the partial-cell !! floor (`Z_FIXED_BED_PARTIAL_MIN`), exactly `z_fixed`'s rule; !! 2. lay the column again from `H + η`: layers `k <= n_f` at !! `h_min`, every live interface at the DILATED nominal depth !! `s·Z_k` below the free surface, with !! `s = (H + η − n_f·h_min)/(H − n_f·h_min)`. !! !! `s` is MOM6's `stretching` with the filler stack taken out of the !! dilation (MOM6 dilates all of `H` and then clamps the fillers back !! to `min_thickness`, which leaves `(s−1)·n_f·h_min` in the partial !! cell — under 1 mm per metre of `η` on the 1-degree Southern Ocean, !! `python_prototypes/mom6_zstar/mom6_zstar.py`). Consequences: !! !! * the liveness of every layer is decided at `η = 0`, so the !! pattern is static BY CONSTRUCTION and the static face mask !! (`zfixed_closed_faces`) applies with every consumer unchanged; !! * every live layer, the partial bed cell included, is its !! `η = 0` thickness times `s` (to round-off) — the dilation keeps !! ratios, so a partial cell (`p0 > Z_FIXED_BED_PARTIAL_MIN` at !! `η = 0` by construction) stays above `H_VANISHED` for every !! `s > H_VANISHED/p0`, i.e. for every `η` that does not all but !! dry the column; !! * at `η = 0`, `s == 1` exactly (numerator and denominator are the !! same expression), `s·Z_k == Z_k`, and the walk is `z_fixed`'s !! operation for operation: the `η = 0` z* target IS the `η = 0` !! `z_fixed` target, bit for bit. !! !! `Σ_k target_h = H + η` by construction (each step assigns what it !! takes from `z_below`, and the surface layer closes the column). !! A degenerate column (`H − n_f·h_min <= h_min`: land, or thinner !! than its filler stack) is laid with `s = 1`, `z_fixed`'s own !! degenerate overshoot. `s` is floored at 0: a column drained past !! its fillers is a dry column, which this coordinate does not !! support (`validate_config` refuses it with wet/dry). integer, intent(in) :: nx !! i-extent of every array (total, incl. halos). integer, intent(in) :: ny !! j-extent of every array (total, incl. halos). integer, intent(in) :: nz !! Number of layers; `k = 1` is the bed, `k = nz` the surface. real(wp), intent(out) :: target_h(nx, ny, nz) !! Target layer thickness (m), bottom-up. real(wp), intent(in) :: total_h(nx, ny) !! Column reference thickness `H` (m) — `Σ h_layer − η`. real(wp), intent(in) :: eta(nx, ny) !! Free-surface anomaly `η` (m). real(wp), intent(in) :: h_nominal !! Uniform nominal spacing `z_fixed_h_ref/nz` (m). Unused when !! `use_profile`. logical, intent(in) :: use_profile !! Take the nominal interfaces from `zi`. real(wp), intent(in) :: zi(0:nz) !! Nominal interface depths (m), bottom-up, `zi(k)` = top of layer !! `k`, `zi(nz) = 0`. Read only when `use_profile`. real(wp), intent(in) :: h_min !! Inert-filler thickness (`zstar_h_min`, `<= H_VANISHED`). integer :: i, j, k, n_f real(wp) :: z_below_loc, z_nom_loc, z_above_loc, stretch_loc, l0_loc real(wp) :: bed_partial_min logical :: walking_loc bed_partial_min = max(h_min, Z_FIXED_BED_PARTIAL_MIN) do concurrent(j=1:ny, i=1:nx) & local(k, n_f, z_below_loc, z_nom_loc, z_above_loc, stretch_loc, l0_loc, & walking_loc) ! Pass 1 — the eta = 0 bed fillers (z_fixed's bed rule, z_top = 0). n_f = 0 z_below_loc = total_h(i, j) walking_loc = .true. do k = 1, nz - 1 if (walking_loc) then if (use_profile) then z_nom_loc = zi(k) else z_nom_loc = real(nz - k, wp)*h_nominal end if if (z_nom_loc >= z_below_loc - bed_partial_min) then n_f = n_f + 1 z_below_loc = z_below_loc - h_min else walking_loc = .false. end if end if end do ! The dilation of the live column. l0_loc = total_h(i, j) - real(n_f, wp)*h_min if (l0_loc > h_min) then stretch_loc = max(((total_h(i, j) + eta(i, j)) - real(n_f, wp)*h_min)/l0_loc, & 0.0_wp) else stretch_loc = 1.0_wp end if ! Pass 2 — the z_fixed walk on the dilated nominal interfaces. z_below_loc = total_h(i, j) + eta(i, j) do k = 1, nz if (k <= n_f) then target_h(i, j, k) = h_min z_below_loc = z_below_loc - h_min else if (k == nz) then ! The surface layer closes the column (z_fixed's top rule). if (0.0_wp > z_below_loc - h_min) then target_h(i, j, k) = h_min else target_h(i, j, k) = z_below_loc end if else if (use_profile) then z_nom_loc = zi(k) else z_nom_loc = real(nz - k, wp)*h_nominal end if z_above_loc = stretch_loc*z_nom_loc target_h(i, j, k) = z_below_loc - z_above_loc z_below_loc = z_above_loc end if end do end do end subroutine ocean_vcoord_zstar_target