rdb_ocean_vdiff Module


Uses

  • module~~rdb_ocean_vdiff~~UsesGraph module~rdb_ocean_vdiff rdb_ocean_vdiff iso_fortran_env iso_fortran_env module~rdb_ocean_vdiff->iso_fortran_env module~rdb_constants rdb_constants module~rdb_ocean_vdiff->module~rdb_constants module~rdb_eos rdb_eos module~rdb_ocean_vdiff->module~rdb_eos module~rdb_grid rdb_grid module~rdb_ocean_vdiff->module~rdb_grid module~rdb_mem_report rdb_mem_report module~rdb_ocean_vdiff->module~rdb_mem_report module~rdb_multilayer_state rdb_multilayer_state module~rdb_ocean_vdiff->module~rdb_multilayer_state module~rdb_scratch_3d rdb_scratch_3d module~rdb_ocean_vdiff->module~rdb_scratch_3d module~rdb_tracer rdb_tracer module~rdb_ocean_vdiff->module~rdb_tracer pic_logger pic_logger module~rdb_ocean_vdiff->pic_logger pic_types pic_types module~rdb_constants->pic_types module~rdb_eos->module~rdb_constants module~rdb_eos->module~rdb_grid module~rdb_grid->module~rdb_constants module~rdb_mem_report->iso_fortran_env module~rdb_mem_report->module~rdb_constants module~rdb_mem_report->pic_logger pic_strings pic_strings module~rdb_mem_report->pic_strings module~rdb_multilayer_state->iso_fortran_env module~rdb_multilayer_state->module~rdb_constants module~rdb_multilayer_state->module~rdb_grid module~rdb_multilayer_state->module~rdb_mem_report module~rdb_multilayer_state->module~rdb_tracer module~rdb_multilayer_state->pic_logger module~rdb_efp rdb_efp module~rdb_multilayer_state->module~rdb_efp module~rdb_error_ring rdb_error_ring module~rdb_multilayer_state->module~rdb_error_ring module~rdb_scratch_3d->iso_fortran_env module~rdb_scratch_3d->module~rdb_constants module~rdb_scratch_3d->module~rdb_mem_report module~rdb_tracer->iso_fortran_env module~rdb_tracer->module~rdb_constants module~rdb_tracer->module~rdb_grid module~rdb_tracer->module~rdb_mem_report module~rdb_efp->iso_fortran_env ieee_arithmetic ieee_arithmetic module~rdb_efp->ieee_arithmetic module~rdb_error_ring->pic_logger

Used by

  • module~~rdb_ocean_vdiff~~UsedByGraph module~rdb_ocean_vdiff rdb_ocean_vdiff module~rdb_ocean_dyn rdb_ocean_dyn module~rdb_ocean_dyn->module~rdb_ocean_vdiff module~rdb_ocean_setup rdb_ocean_setup module~rdb_ocean_setup->module~rdb_ocean_vdiff 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_ocean_state->module~rdb_ocean_vdiff module~rdb_ocean_state->module~rdb_ocean_dyn module~rdb_driver rdb_driver module~rdb_driver->module~rdb_ocean_dyn module~rdb_driver->module~rdb_ocean_state module~rdb_ocean_engine rdb_ocean_engine module~rdb_driver->module~rdb_ocean_engine module~rdb_handle rdb_handle module~rdb_handle->module~rdb_ocean_state module~rdb_handle->module~rdb_ocean_engine module~rdb_ocean_api rdb_ocean_api module~rdb_ocean_api->module~rdb_ocean_dyn 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_api->module~rdb_ocean_engine 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 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

Variables

Type Visibility Attributes Name Initial
integer, public, parameter :: BBL_FORM_LINEAR = 1

&ocean_bdrag_nml form = "linear" under the MOM6 BBL: MOM6 LINEAR_DRAG, u* = sqrt(cd)·DRAG_BG_VEL (see bbl_cd).

integer, public, parameter :: BBL_FORM_QUADRATIC = 2

form = "quadratic": u* = sqrt(cd)·u_bbl, u_bbl the HBBL-mean speed with the background velocity added in quadrature.


Derived Types

type, public ::  ocean_vdiff_t

Components

Type Visibility Attributes Name Initial
real(kind=wp), public :: K_v_momentum = 0.0_wp

Constant momentum vertical viscosity (m^2/s). Zero is a no-op.

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

Constant tracer vertical diffusivity (m^2/s). Zero is a no-op.

type(scratch_3d_buffer_t), public :: a_diag_t

Sub-diagonal for the tracer solve. Shape (nx, ny, nz).

type(scratch_3d_buffer_t), public :: a_diag_u

East-face (u) tridiagonal scratch.

type(scratch_3d_buffer_t), public :: a_diag_v

North-face (v) tridiagonal scratch.

type(scratch_3d_buffer_t), public :: b_diag_t

Main diagonal.

type(scratch_3d_buffer_t), public :: b_diag_u

East-face (u) tridiagonal scratch.

type(scratch_3d_buffer_t), public :: b_diag_v

North-face (v) tridiagonal scratch.

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

MOM6 DRAG_BG_VEL (&ocean_bdrag_nml bg_vel).

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

MOM6 CDRAG. Quadratic: &ocean_bdrag_nml cd. Linear: the equivalent r·hbbl/bg_vel, so that CDRAG·DRAG_BG_VEL = r·hbbl — the stress of the HBBL-distributed linear drag, r·hbbl·u.

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

Cell salinity concentration, as bbl_conc_t.

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

Cell temperature CONCENTRATION (nx, ny, nz) for the BBL stratification measure, read through the I1′ column rule (rdb_vl_column_conc: a filler reads its donor). Workspace of vdiff_set_viscous_bbl; (1,1,1) unless bbl_per_face.

integer, public :: bbl_form = BBL_FORM_QUADRATIC

Drag law of the BBL (BBL_FORM_*).

logical, public :: bbl_glue = .false.

MOM6 bottomdraglaw parity for the interface coupling (&ocean_vdiff_nml bbl_glue, PGF_BUG.md §9). MOM6 holds a layered basin at rest NOT by computing a clean PGF — its PFu over grounded/sliver layers is identical to ours — but by absorbing it viscously every step (du_dt_visc = −PFu to machine precision, measured): find_coupling_coef raises the interface coupling to a_cpl = kv_bbl/h_shear within the bottom boundary layer (botfn weight, z_i accumulated from HARMONIC thicknesses so grounded stacks sit at z≈0), and the BED coupling is the piston kv_bbl/(h₁/2) — unbounded as the bottom layer thins (measured a_cpl 3e7–1.3e8 m/s on the rest-state reproducer). This knob ports both halves into the momentum tridiagonal: * interface kv → kv + (kv_bbl − KV)·botfn (KV = kv_bbl_bg), with h_shear capped toward bbl_thick under the same botfn weight; * bed row → dt·kv_bbl/(hf₁·(min(hvel₁/2, bbl_thick))) — THE bed sink whenever the glue is on: it replaces the dt·λ_bot Rayleigh fold, and the driver skips the explicit drag apply (the explicit du_drag still feeds the barotropic F_slow, the split implicit_drag takes). kv_bbl and bbl_thick are PER FACE: vdiff_set_viscous_bbl (MOM6 set_viscous_BBL, quadratic or linear drag law, KW99 rotation/stratification-limited thickness) fills them once per outer step when bbl_per_face (configure: a bottom drag is configured). A hand-built slot without bbl_per_face gets the historical constants bbl_piston·hbbl_visc / hbbl_visc. Requires hvel_mom6 (supplies the height-above-bed bookkeeping), enforced at configure. With no bottom drag configured the configure latch turns it off (nothing to glue).

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

MOM6 HBBL (&ocean_bdrag_nml hbbl): the thickness over which the near-bottom velocity is averaged for the drag law.

logical, public :: bbl_per_face = .false.

The MOM6 bottom boundary layer (set_viscous_BBL) is computed per face once per outer step by vdiff_set_viscous_bbl into kv_bbl_u/v and bbl_thick_u/v, which the glue then reads. Set at configure when bbl_glue is on AND a bottom drag is configured with &ocean_bdrag_nml hbbl > 0 (MOM6 requires HBBL). .false. ⇒ the glue uses the historical constants bbl_piston·hbbl_visc / hbbl_visc, refilled each call (the hand-built test path).

real(kind=wp), public :: bbl_piston = 3.0e-4_wp

BBL drag piston velocity u* (m/s) for bbl_glue — MOM6 CDRAG·DRAG_BG_VEL (0.003·0.1 with reference defaults). kv_bbl = bbl_piston·hbbl_visc.

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

Boussinesq reference density of the BBL stratification measure.

logical, public :: bbl_rino_cap = .false.

MOM6 RiNo_mix (= kappa-shear on): the BBL is capped at 0.5·HBBL.

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

MOM6 BBL_THICK_MIN (&ocean_bdrag_nml bbl_thick_min; MOM6 default 0).

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

BBL thickness (m) at u-faces, (nx+1, ny).

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

BBL thickness (m) at v-faces, (nx, ny+1).

type(scratch_3d_buffer_t), public :: c_diag_t

Super-diagonal (overwritten by c_prime).

type(scratch_3d_buffer_t), public :: c_diag_u

East-face (u) tridiagonal scratch.

type(scratch_3d_buffer_t), public :: c_diag_v

North-face (v) tridiagonal scratch.

real(kind=wp), public :: hbbl_visc = 10.0_wp

Bottom-boundary-layer scale for the botfn blend (MOM6 HBBL, 10.0 m in the double_gyre reference) when the BBL glue is off. Under the per-face glue the blend is normalised by each face’s bbl_thick instead (MOM6 I_Hbbl = 1/bbl_thick), and this is only the HBBL fallback for a drag configured with &ocean_bdrag_nml hbbl = 0 (MOM6 has ONE HBBL).

logical, public :: hvel_harmonic = .false.

Which MOM6 face-thickness branch hvel_mom6 builds. .false. (default) is MOM6’s DEFAULT, HARMONIC_VISC = False (vertvisc_coef): the face thickness is the ARITHMETIC mean, blended toward the harmonic mean near the bed when the flow runs from the thin side to the thick one, and the height above the bed is max(zh, z_clear) — z_clear the height of the higher of the two cells’ interfaces above the SHALLOWER of the two beds. So every face layer below the shallower bed of a step sits at z_i ≈ 0, inside the bottom boundary layer, and the BBL glue couples it. .true. is MOM6 HARMONIC_VISC = True: harmonic face thickness, blended toward arithmetic when the flow runs thick -> thin, height above bed from harmonic thicknesses alone (the historical roundabout hvel_mom6, gated by hvel_upwind).

logical, public :: hvel_mom6 = .false.

MOM6 HARMONIC_VISC = True parity for the MOMENTUM face thickness (hvel). Roundabout’s h_u is the ARITHMETIC mean unconditionally while MOM6’s is the HARMONIC mean blended back toward arithmetic near the bed when the flow runs thick->thin – and MOM6’s h_shear is then the ARITHMETIC mean of those hvels. The two means are effectively SWAPPED relative to MOM6. For a grounded sliver harm(1e-10, 20) ~ 2e-10 against arith ~ 10: eleven orders, and that gap IS the ~1e19 suppression that renders the spurious sliver PGF harmless in MOM6.

Read more…
logical, public :: hvel_upwind = .true.

Near-bed upwind (arithmetic-donor) blend inside the hvel_mom6 face-thickness build. .true. = MOM6 parity (the historical hvel_mom6 behaviour, bit-identical). .false. = pure harmonic hvel: the blend’s u-sign test flip-flops on roundoff velocities at (near-)rest and collapses the BBL glue at flipped faces (PGF_BUG.md §9.8) — the rest-state envelope runs with it off.

logical, public :: implicit_drag = .false.

Fold the quadratic / linear bottom drag into the backward-Euler vertical-friction tridiagonal as a stress bottom-BC diagonal coupling (&ocean_vdiff_nml implicit_drag). Roundabout bed is k = 1: the drag rate λ_bot adds to the b_diag(:, :, 1) diagonal only (a positive add ⇒ unconditionally stable on thin bottom layers where the explicit u·(1 − dt·c_d|U|/h) reverses sign once dt·c_d|U|/h > 2). λ_bot is the SAME Rayleigh rate (c_d·|U_bbl|/h_1 quadratic, r linear, |U| frozen at uⁿ) the bottom-drag slot already forms; the row is already normalized by h_1 so the diagonal increment is dt·λ_bot directly. Mutually exclusive with &ocean_bdrag_nml implicit (configure fails loud); the explicit drag apply is gated off when on. Default .false. ⇒ bit-identical.

logical, public :: implicit_stress = .false.

Fold the surface wind stress into the backward-Euler vertical- friction tridiagonal as a Neumann top-BC right-hand-side term (&ocean_vdiff_nml implicit_stress). Roundabout is bottom-up, so the SURFACE is k = nz: the kinematic stress τ/ρ₀ adds to the rhs(:, :, nz) row only (the diagonal is unchanged), filtered through the implicit operator together with the interior shear. When .true. the explicit surface-stress pre-solve apply in the dyn run-stage is suppressed (the driver gates it) so the forcing is not double-counted. Default .false. ⇒ matrix + RHS built exactly as before ⇒ bit-identical to the explicit path.

logical, public :: implicit_top_drag = .false.

Fold the ICE-SHELF TOP drag into the k = nz DIAGONAL (&ocean_vdiff_nml implicit_top_drag) instead of the explicit pre-solve add, and MASK the wind-stress RHS off on the faces the ice covers. The mirror of implicit_drag at the other end of the column: a drag is a diagonal term and the wind is an RHS term, so the two share the surface row without either being approximated, and the cover mask is what makes the sharing physical (there is no atmosphere under a shelf). λ_top is the SAME Rayleigh rate (C_d·|U|/h_nz quadratic, r linear, |U| frozen at uⁿ) the top-drag slot forms, and it arrives already cover- and wet-masked. Requires &ocean_tdrag_nml enable; mutually exclusive with &ocean_tdrag_nml implicit and with htbl > 0; all fail loud at configure. The explicit top-drag apply is gated off when on. Default .false. ⇒ bit-identical.

logical, public :: is_init = .false.

True between init and destroy.

real(kind=wp), public :: kv_bbl_bg = 1.0e-4_wp

MOM6 KV: the background viscosity the BBL viscosity REPLACES within the boundary layer, Kv_tot = Kv_tot + (kv_bbl − KV)·botfn (find_coupling_coef) — the shear / boundary-layer contributions are kept. Set from &ocean_vmix_nml kv_bg.

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

BBL viscosity kv_bbl (m²/s) at u-faces, (nx+1, ny).

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

BBL viscosity at v-faces, (nx, ny+1).

type(scratch_3d_buffer_t), public :: kv_scalar_buf

Diffusivity workspace used when no 3D source is provided.

type(scratch_3d_buffer_t), public :: rhs_t

Right-hand side / solution.

type(scratch_3d_buffer_t), public :: rhs_u

East-face (u) tridiagonal scratch.

type(scratch_3d_buffer_t), public :: rhs_v

North-face (v) tridiagonal scratch.

logical, public :: use_harmonic = .false.

MOM6 HARMONIC_VISC analogue. When .true. the implicit vdiff solver uses the harmonic mean of adjacent layer thicknesses in the face-thickness denominator instead of the arithmetic mean. Better-conditioned at thin / vanishing layers — harm = 2·h_a·h_b / (h_a + h_b) stays small when one neighbour is small, whereas arith = 0.5·(h_a + h_b) is dominated by the thicker neighbour and produces stiff coefficients that the Thomas solve can’t handle gracefully. Default .false. preserves the arithmetic-mean behaviour bit-identically.

logical, public :: zlevel_faces = .false.

&vcoord_nml zfixed_closed_faces — z-level partial steps. Latched by configure_ocean_closed_faces, forwarded as a plain scalar to diffuse_velocity_columns_impl, where it switches the face column to min(h_L, h_R) and CUTS the tridiagonal coupling across any interface touching a closed (inert-filler-on-one-side) layer. See that routine’s zlevel_faces docstring for the full argument. Default .false. ⇒ bit-identical.

Type-Bound Procedures

procedure, public, non_overridable :: bytes => ocean_vdiff_bytes
procedure, public, non_overridable :: destroy => ocean_vdiff_destroy
procedure, public, non_overridable :: enter_data => ocean_vdiff_enter_data
procedure, public, non_overridable :: exit_data => ocean_vdiff_exit_data
procedure, public, non_overridable :: init => ocean_vdiff_init

Functions

public pure function face_thick(h_a, h_b, use_harmonic) result(dz)

Public only for the unit-test suite (no production module imports it); ignore when developing production code in other modules. Face-thickness for the vdiff implicit operator.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: h_a
real(kind=wp), intent(in) :: h_b
logical, intent(in) :: use_harmonic

Return Value real(kind=wp)

private pure function ocean_vdiff_bytes(this) result(nbytes)

Counted allocatable footprint of the implicit vertical diffusion slot (0 when unallocated).

Arguments

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

Return Value integer(kind=int64)


Subroutines

public subroutine vdiff_apply_momentum(grid, this, ms, dt, kv_source, tau_u, tau_v, lambda_bot_u, lambda_bot_v, rho0, visc_rem_u, visc_rem_v, remnant_only, kv_corner_source, kv_corner_prandtl, lambda_top_u, lambda_top_v, cover_u, cover_v)

Backward-Euler vertical viscosity applied to ms%u_face_x_layer and ms%v_face_y_layer. Per-face h_face averaged from the two abutting cell columns.

Read more…

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_vdiff_t), intent(inout) :: this
type(multilayer_state_t), intent(inout) :: ms
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(in), optional :: kv_source(grid%nx_total,grid%ny_total,ms%nz_ml+1)
real(kind=wp), intent(in), optional :: tau_u(grid%nx_total+1,grid%ny_total)
real(kind=wp), intent(in), optional :: tau_v(grid%nx_total,grid%ny_total+1)
real(kind=wp), intent(in), optional :: lambda_bot_u(grid%nx_total+1,grid%ny_total)
real(kind=wp), intent(in), optional :: lambda_bot_v(grid%nx_total,grid%ny_total+1)
real(kind=wp), intent(in), optional :: rho0

Boussinesq reference density for the implicit surface-stress fold, dt*(tau/rho0)/h_nz. READ ONLY when that fold is active (do_stress below); every other path never reaches it. There is deliberately NO defaulted value: the one rho0 of record is &ocean_ic_nml rho_0 -> eos%rho0, which the production call site hands over as surface_stress%rho0 (configure_ocean_reference_density). Omitting it WITH the fold active is a wiring bug and fails loud — it used to fall back to a silent literal 1035, which is how a run configured at a different rho_0 could fold its wind stress in on the wrong one.

real(kind=wp), intent(inout), optional :: visc_rem_u(grid%nx_total+1,grid%ny_total,ms%nz_ml)
real(kind=wp), intent(inout), optional :: visc_rem_v(grid%nx_total,grid%ny_total+1,ms%nz_ml)

Explicit-shape for the reason spelled out on the argument list above.

logical, intent(in), optional :: remnant_only

.true. = build the matrices and (re)fill visc_rem_u/v WITHOUT solving for / modifying the velocities — the pre-substep visc_rem refresh (PGF_BUG.md §9; MOM6 computes vertvisc_coef before btstep, so its BT weights never lag). Requires both visc_rem_u/v present. Default .false..

real(kind=wp), intent(in), optional :: kv_corner_source(grid%nx_total+1,grid%ny_total+1,ms%nz_ml+1)

Corner-staggered interface viscosity source, (nx+1, ny+1, nz+1) — see the corner add-on note above. Typically the vertex kappa-shear kd_corner carrier (device-resident). Explicit-shape for the reason spelled out on the argument list above.

real(kind=wp), intent(in), optional :: kv_corner_prandtl

Scale applied to kv_corner_source (Kv = Pr·Kd; the vertex kappa-shear prandtl_turb). Default 1.

real(kind=wp), intent(in), optional :: lambda_top_u(grid%nx_total+1,grid%ny_total)
real(kind=wp), intent(in), optional :: lambda_top_v(grid%nx_total,grid%ny_total+1)
real(kind=wp), intent(in), optional :: cover_u(grid%nx_total+1,grid%ny_total)
real(kind=wp), intent(in), optional :: cover_v(grid%nx_total,grid%ny_total+1)

public subroutine vdiff_apply_tracers(grid, this, ms, dt, kt_source, ks_source)

Backward-Euler vertical diffusivity on every registered tracer. Each tracer is converted to T = hTr/h, the tridiagonal solve runs, then hTr = T*h is reconstituted. h_layer is untouched. Per-tracer do_vertical_diffusion flag gates participation.

Read more…

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_vdiff_t), intent(inout) :: this
type(multilayer_state_t), intent(inout) :: ms
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(in), optional :: kt_source(:,:,:)

Temperature diffusivity, (nx, ny, nz+1), interface-located.

real(kind=wp), intent(in), optional :: ks_source(:,:,:)

Salinity + passive-tracer diffusivity, same shape. MUST be accompanied by kt_source (case 2 fails loud).

public subroutine vdiff_bbl_configure(this, nx, ny, nz, form, cd, r_linear, hbbl, bg_vel, thick_min, rino_cap, rho0, kv_bg)

Latch the MOM6 per-face bottom boundary layer (bbl_per_face) from the bottom-drag configuration and size its workspace. Called once at configure, BEFORE enter_data, by rdb_ocean_setup when bbl_glue is on. MOM6 set_visc_init is the parameter map:

Read more…

Arguments

Type IntentOptional Attributes Name
class(ocean_vdiff_t), intent(inout) :: this
integer, intent(in) :: nx

Cell-centred extents (ghosts included) of the workspace.

integer, intent(in) :: ny

Cell-centred extents (ghosts included) of the workspace.

integer, intent(in) :: nz

Cell-centred extents (ghosts included) of the workspace.

integer, intent(in) :: form

BBL_FORM_LINEAR or BBL_FORM_QUADRATIC.

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

Quadratic drag coefficient (dimensionless).

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

Linear drag rate (1/s) of the HBBL-distributed linear drag.

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

MOM6 HBBL (m).

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

MOM6 DRAG_BG_VEL (m/s).

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

MOM6 BBL_THICK_MIN (m).

logical, intent(in) :: rino_cap

MOM6 RiNo_mix (kappa-shear on).

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

Boussinesq reference density (kg/m^3).

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

MOM6 KV, the background viscosity (m^2/s).

public subroutine vdiff_set_viscous_bbl(grid, this, ms, eos, f_corner)

MOM6 set_viscous_BBL (the BOTTOMDRAGLAW branch): the per-face bottom-boundary-layer viscosity kv_bbl_u/v and thickness bbl_thick_u/v that the vertical-friction glue reads. Called ONCE per outer step, before the stage loop, from the state at the start of the step — MOM6 calls it once per step, from step_MOM_dynamics, before the predictor. No-op unless bbl_glue .and. bbl_per_face.

Read more…

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_vdiff_t), intent(inout) :: this
type(multilayer_state_t), intent(in) :: ms
type(eos_t), intent(in) :: eos

The run’s equation of state, for ∂ρ/∂T and ∂ρ/∂S.

real(kind=wp), intent(in) :: f_corner(grid%nx_total+1,grid%ny_total+1)

Coriolis parameter at cell SW corners (1/s), cor%f_corner.

private pure subroutine apply_factored_tracer(nx, ny, nz, h_layer, hTr, a_diag, b_diag, c_diag, rhs, budget)

Apply the pre-factored tridiagonal (from build_factorize_tracer_matrix) to one tracer: form T = hTr/h, run the Thomas RHS sweep + back-substitution against the stored pivots, and reconstitute hTr = T_new * h_layer. Operates on concentration so it conserves cell-centred T; h_layer and the factored a/b/c are read-only (shared across every tracer).

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h_layer(nx,ny,nz)
real(kind=wp), intent(inout) :: hTr(nx,ny,nz)
real(kind=wp), intent(in) :: a_diag(nx,ny,nz)
real(kind=wp), intent(in) :: b_diag(nx,ny,nz)
real(kind=wp), intent(in) :: c_diag(nx,ny,nz)
real(kind=wp), intent(inout) :: rhs(nx,ny,nz)
real(kind=wp), intent(inout), optional :: budget(nx,ny,nz)

private pure subroutine bbl_column_conc_impl(nx, ny, nz, h, htr, conc)

Cell concentrations of one tracer through THE vanished-layer column rule (rdb_vl_column_conc): hTr/h on a live layer, the donor’s concentration on a filler, never a quotient by a near-zero or negative thickness.

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
real(kind=wp), intent(in) :: h(nx,ny,nz)
real(kind=wp), intent(in) :: htr(nx,ny,nz)
real(kind=wp), intent(inout) :: conc(nx,ny,nz)

private pure subroutine bbl_faces_impl(nu, nv, nx, ny, nz, x_face, u_x, v_y, h, wet, conc_t, conc_s, ncx, ncy, ncz, f_corner, use_eos, eos, form, cd, hbbl, bg_vel, thick_min, rino_cap, rho0, kv_bbl, bbl_thick)

Per-face body of vdiff_set_viscous_bbl (see there). x_face selects the u-faces (nx+1, ny) (cells i-1, i) or the v-faces (nx, ny+1) (cells j-1, j).

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nu
integer, intent(in) :: nv
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
logical, intent(in) :: x_face
real(kind=wp), intent(in) :: u_x(nx+1,ny,nz)
real(kind=wp), intent(in) :: v_y(nx,ny+1,nz)
real(kind=wp), intent(in) :: h(nx,ny,nz)
real(kind=wp), intent(in) :: wet(nx,ny)
real(kind=wp), intent(in) :: conc_t(ncx,ncy,ncz)
real(kind=wp), intent(in) :: conc_s(ncx,ncy,ncz)

Cell T/S concentrations; read only when use_eos (then (nx, ny, nz); a (1,1,1) placeholder otherwise).

integer, intent(in) :: ncx
integer, intent(in) :: ncy
integer, intent(in) :: ncz
real(kind=wp), intent(in) :: f_corner(nx+1,ny+1)
logical, intent(in) :: use_eos
type(eos_t), intent(in) :: eos
integer, intent(in) :: form
real(kind=wp), intent(in) :: cd
real(kind=wp), intent(in) :: hbbl
real(kind=wp), intent(in) :: bg_vel
real(kind=wp), intent(in) :: thick_min
logical, intent(in) :: rino_cap
real(kind=wp), intent(in) :: rho0
real(kind=wp), intent(inout) :: kv_bbl(nu,nv)
real(kind=wp), intent(inout) :: bbl_thick(nu,nv)

private pure subroutine build_factorize_tracer_matrix(nx, ny, nz, use_harmonic, dt, kv_centre, h_layer, a_diag, b_diag, c_diag)

Build the backward-Euler tridiagonal per cell column and run the Thomas forward factorization — the tracer-INDEPENDENT half of the vertical-diffusion solve (depends only on kv/h/dt). Run once per stage; every registered tracer then reuses the factored coefficients via apply_factored_tracer.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz
logical, intent(in) :: use_harmonic
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(in) :: kv_centre(nx,ny,nz+1)
real(kind=wp), intent(in) :: h_layer(nx,ny,nz)
real(kind=wp), intent(inout) :: a_diag(nx,ny,nz)
real(kind=wp), intent(inout) :: b_diag(nx,ny,nz)
real(kind=wp), intent(inout) :: c_diag(nx,ny,nz)

private pure subroutine diffuse_velocity_columns_impl(nu, nv, nz, dt, u_face, h_layer, kv_centre, wet_cell, x_face, nx_cells, ny_cells, use_harmonic, a_diag, b_diag, c_diag, rhs, do_stress, do_drag, rho0, tau_face, lambda_bot, do_top, lambda_top, cover_face, solve_momentum, do_remnant, visc_rem_out, hvel_mom6, hbbl_visc, bbl_glue, hvel_upwind, hvel_harmonic, kv_bbl_face, bbl_thick_face, kv_bbl_bg, do_corner, kv_prandtl, kv_corner, zlevel_faces, k_top_face, k_bot_face)

Build + solve the tridiagonal system per face column. u_face is either u_face_x_layer (x_face = .true., shape (nx+1, ny)) or v_face_y_layer (x_face = .false., shape (nx, ny+1)). h_layer is cell-centred (nx, ny, nz) and we average across the face direction to get the face thickness. kv_centre is the same cell-centred diffusivity field used by the tracer kernel — we average it across the face to get a face-located value at each interface.

Read more…

Arguments

Type IntentOptional Attributes Name
integer, intent(in) :: nu
integer, intent(in) :: nv
integer, intent(in) :: nz
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(inout) :: u_face(nu,nv,nz)
real(kind=wp), intent(in) :: h_layer(nx_cells,ny_cells,nz)
real(kind=wp), intent(in) :: kv_centre(nx_cells,ny_cells,nz+1)
real(kind=wp), intent(in) :: wet_cell(nx_cells,ny_cells)

Cell-centred wet/dry land mask (1 wet, 0 land). The wind-stress fold multiplies tau_face by the face mask min of the two bounding cells — matching the explicit surface-stress kernel — so no stress is injected at a no-normal-flow land face. All-wet (mask ≡ 1) ⇒ bit-identical. Only read when do_stress.

logical, intent(in) :: x_face
integer, intent(in) :: nx_cells
integer, intent(in) :: ny_cells
logical, intent(in) :: use_harmonic
real(kind=wp), intent(inout) :: a_diag(nu,nv,nz)
real(kind=wp), intent(inout) :: b_diag(nu,nv,nz)
real(kind=wp), intent(inout) :: c_diag(nu,nv,nz)
real(kind=wp), intent(inout) :: rhs(nu,nv,nz)
logical, intent(in) :: do_stress

Fold the surface wind stress into the k = nz RHS row.

logical, intent(in) :: do_drag

Fold the bottom drag into the k = k_bot_face diagonal.

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

Boussinesq reference density for the τ/ρ₀ stress conversion.

real(kind=wp), intent(in), optional :: tau_face(nu,nv)

Wind stress (N/m²) on this face. Present iff do_stress.

real(kind=wp), intent(in), optional :: lambda_bot(nu,nv)

Bottom-drag Rayleigh rate λ (1/s) on this face. Present iff do_drag.

logical, intent(in) :: do_top

Fold the ice-shelf top drag into the k = nz diagonal AND mask the wind-stress RHS by (1 − cover_face).

real(kind=wp), intent(in), optional :: lambda_top(nu,nv)

Top-drag Rayleigh rate λ (1/s) on this face. Present iff do_top.

real(kind=wp), intent(in), optional :: cover_face(nu,nv)

Face ice-cover mask (0 open, 1 under ice), the OR of the two abutting cells (rdb_ocean_top_drag). Present iff do_top.

logical, intent(in) :: solve_momentum

.false. = remnant-only mode: build the matrix and compute visc_rem_out WITHOUT touching u_face (the pre-substep visc_rem refresh, PGF_BUG.md §9 — MOM6 computes vertvisc_coef before btstep every stage, so its BT weights never lag; the stage-end producer alone leaves visc_rem ≡ 1 for the whole first stage, and the Δu corrector then deposits the spurious column-mean into wet layers). .true. = the normal solve.

logical, intent(in) :: do_remnant

Fill visc_rem_out with the viscous remnant γ_k — the sensitivity of the post-friction layer velocity to a uniform barotropic acceleration, γ_k ≡ (1/Δt)·∂u_k^{n+1}/∂Ā. γ solves the SAME tridiagonal system as the momentum solve (same a_diag/b_diag, and the already-factorized c_diag left behind by the momentum forward sweep) with RHS ≡ 1 — Roundabout’s rows are pre-normalized by h_k, so the remnant RHS is the unit vector, NOT h_u(k) (MOM6’s un-normalized convention). Reusing the surviving factorization means γ cannot drift from the operator actually solved for momentum.

real(kind=wp), intent(inout), optional :: visc_rem_out(nu,nv,nz)

Output γ_k, bottom-up (k = 1 bed → γ smallest, k = nz surface → γ → 1). Clamped to min(γ, 1.0) at production (cheap FP insurance on a quantity the maximum principle already bounds to (0, 1]). Present iff do_remnant.

logical, intent(in) :: hvel_mom6

MOM6 HARMONIC_VISC parity for h_u + h_shear (see the slot-type docstring). .false. => the historical arithmetic-h_u / face_thick-dz pair, bit-identical.

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

Bottom-layer scale for the botfn blend (MOM6 HBBL).

logical, intent(in) :: bbl_glue

MOM6 bottomdraglaw coupling parity (see the slot-type docstring). Configure guarantees hvel_mom6 when this is on; the bed piston then applies whether or not do_drag. .false. ⇒ bit-identical.

logical, intent(in) :: hvel_upwind

.false. = skip the near-bed upwind blend (pure harmonic hvel). See the slot-type docstring. hvel_harmonic branch only.

logical, intent(in) :: hvel_harmonic

MOM6 HARMONIC_VISC: .false. = MOM6’s default z_clear face-thickness branch, .true. = the harmonic branch (slot-type docstring). Only read when hvel_mom6.

real(kind=wp), intent(in) :: kv_bbl_face(nu,nv)

BBL viscosity kv_bbl (m²/s) of this face (MOM6 visc%Kv_bbl_u). Only read when bbl_glue.

real(kind=wp), intent(in) :: bbl_thick_face(nu,nv)

BBL thickness (m) of this face (MOM6 visc%bbl_thick_u); also the height-above-bed normaliser 1/I_Hbbl under the glue, as in MOM6’s bottomdraglaw branch. Only read when bbl_glue.

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

MOM6 KV: the background viscosity the BBL viscosity replaces within botfn reach of the bed, Kv_tot + (kv_bbl − KV)·botfn.

logical, intent(in) :: do_corner

Add the corner-staggered viscosity to every face interface. Gated INSIDE the single DC (no split loop — NVHPC penalty); kv_corner is guaranteed present when do_corner.

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

Scale on kv_corner (Kv = Pr·Kd). Only read when do_corner.

real(kind=wp), intent(in), optional :: kv_corner(nx_cells+1,ny_cells+1,nz+1)

Corner-staggered interface viscosity (SW-corner convention: corner (i,j) is the SW corner of cell (i,j)). A face reads the 2-point average of its two END corners: u-face (i,j) → corners (i,j)/(i,j+1); v-face (i,j) → corners (i,j)/(i+1,j) — the direct corner→face route, never via a tracer point. Present iff do_corner.

logical, intent(in) :: zlevel_faces

&vcoord_nml zfixed_closed_faces — z-level partial steps.

Read more…
integer, intent(in) :: k_top_face(nu,nv)

ms%k_top_u / k_top_v — the index of the first layer that is LIVE on BOTH sides of this face, nz wherever nothing vanishes against the top. The surface row of the column solve, the ice-shelf top-drag diagonal fold and the wind-stress RHS fold all sit on THIS row instead of on nz: under a quasi-geopotential coordinate beneath a shelf the rows above it are inert fillers, decoupled to the identity by the zlevel_faces gates, so a dt*lambda_top added at nz would damp a row that is not coupled to the ocean at all — and the drag rate lambda_top is itself captured at k_top_face by rdb_ocean_top_drag, so the two MUST agree. With k_top_face ≡ nz every expression below is the one it replaced, bit for bit.

integer, intent(in) :: k_bot_face(nu,nv)

ms%k_bot_u / k_bot_v — the first layer LIVE on BOTH sides of this face counting UP from the bed (max of its two columns), 1 wherever nothing vanishes against the bed. The bed row of the column solve (no-flux-below BC, the implicit bottom-drag diagonal fold and the bbl_glue piston) sits on THIS row instead of on k = 1, and the rows below it — inert z_fixed bed fillers — are the identity. The drag rate lambda_bot is itself captured at k_bot_face by ocean_bottom_drag_compute_tendencies, so the two MUST agree. The height-above-bed stack zint (BBL glue, upwind blend) also starts accumulating here. With k_bot_face ≡ 1 every expression below is the one it replaced, bit for bit.

private pure subroutine fill_bbl_constants(kv_bbl, bbl_thick, kv_val, thick_val, nu, nv)

The historical constant-piston glue (bbl_per_face = .false.).

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(inout) :: kv_bbl(nu,nv)
real(kind=wp), intent(inout) :: bbl_thick(nu,nv)
real(kind=wp), intent(in) :: kv_val
real(kind=wp), intent(in) :: thick_val
integer, intent(in) :: nu
integer, intent(in) :: nv

private pure subroutine fill_kv_scalar_buf(kv_buf, kappa, nx, ny, nz)

Broadcast the scalar viscosity kappa into the interface-located workspace. Boundary interfaces (bed at k=1, surface at k=nz+1) are forced to zero to match the closed BCs the column solve assumes; the original scalar kernel hard-coded those BCs via α_1 = 0 and β_nz = 0.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(out) :: kv_buf(nx,ny,nz+1)
real(kind=wp), intent(in) :: kappa
integer, intent(in) :: nx
integer, intent(in) :: ny
integer, intent(in) :: nz

private subroutine ocean_vdiff_destroy(this)

Arguments

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

private subroutine ocean_vdiff_enter_data(this)

Arguments

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

private subroutine ocean_vdiff_enter_data_impl(this)

Arguments

Type IntentOptional Attributes Name
type(ocean_vdiff_t), intent(inout) :: this

private subroutine ocean_vdiff_exit_data(this)

Arguments

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

private subroutine ocean_vdiff_exit_data_impl(this)

Arguments

Type IntentOptional Attributes Name
type(ocean_vdiff_t), intent(inout) :: this

private subroutine ocean_vdiff_init(this, grid, nz_ml)

Arguments

Type IntentOptional Attributes Name
class(ocean_vdiff_t), intent(inout) :: this
type(hgrid_t), intent(in) :: grid
integer, intent(in), optional :: nz_ml