Thermodynamics on/off, velocity-truncation clamp (MAXVEL), DIRECT_STRESS surface-stress distribution, KV_ML_INVZ2 surface-band viscosity, HARMONIC_VISC face-thickness mean, and the DT_THERM thermo/tracer cadence — with their rank-0 log lines.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(config_t), | intent(in) | :: | cfg | |||
| type(ocean_state_t), | intent(inout) | :: | ocean_state | |||
| integer, | intent(in) | :: | compute_rank | |||
| integer, | intent(out), | optional | :: | ierr |
Non-zero on a vertical-mixing configuration conflict when
present; absent behaves as today ( |
subroutine configure_ocean_vmix(cfg, ocean_state, compute_rank, ierr) !! Thermodynamics on/off, velocity-truncation clamp (MAXVEL), DIRECT_STRESS !! surface-stress distribution, KV_ML_INVZ2 surface-band viscosity, !! HARMONIC_VISC face-thickness mean, and the DT_THERM thermo/tracer !! cadence — with their rank-0 log lines. type(config_t), intent(in) :: cfg type(ocean_state_t), intent(inout) :: ocean_state integer, intent(in) :: compute_rank integer, intent(out), optional :: ierr !! Non-zero on a vertical-mixing configuration conflict when !! present; absent behaves as today (`error stop`). ocean_state%dyn%enable_thermodynamics = cfg%ocean%thermo%enable_thermodynamics ! Velocity-truncation clamp (MOM6 MAXVEL); 0 = off. ocean_state%dyn%maxvel = cfg%ocean%pgf%maxvel if (compute_rank == 0) then if (cfg%ocean%pgf%maxvel > 0.0_wp) then call logger%info("Velocity clamp: "//to_string(cfg%ocean%pgf%maxvel)//" m/s") end if end if ! Advective-CFL velocity truncation (E7, MOM6 CFL_trunc); 0 = off. ocean_state%dyn%cfl_trunc = cfg%ocean%pgf%cfl_trunc if (compute_rank == 0) then if (cfg%ocean%pgf%cfl_trunc > 0.0_wp) then call logger%info("CFL truncation: threshold "//to_string(cfg%ocean%pgf%cfl_trunc)) end if end if ! DIRECT_STRESS — wind stress spread over the top hmix_stress metres. ocean_state%surface_stress%direct_stress = cfg%ocean%vmix%direct_stress ocean_state%surface_stress%hmix_stress = cfg%ocean%vmix%hmix_stress if (compute_rank == 0) then if (cfg%ocean%vmix%direct_stress) then call logger%info("Surface stress: DIRECT (over top "// & to_string(cfg%ocean%vmix%hmix_stress)//" m)") end if end if ! KV_ML_INVZ2 — extra surface-band vertical viscosity (MOM6). ocean_state%vmix%kv_ml_invz2 = cfg%ocean%vmix%kv_ml_invz2 ocean_state%vmix%hmix_fixed = cfg%ocean%vmix%hmix_fixed if (compute_rank == 0) then if (cfg%ocean%vmix%kv_ml_invz2 > 0.0_wp) then call logger%info("KV_ML_INVZ2: "//to_string(cfg%ocean%vmix%kv_ml_invz2)// & " m²/s in top "//to_string(cfg%ocean%vmix%hmix_fixed)//" m") end if end if ! C11 assembly gate — ceilings / smoothing / guard. Backgrounds ! stay on the slot defaults (= pp81_*_bg, set in ocean_vmix_init) ! so the floor is a no-op for the closure path; only the ceilings / ! smoothing / guard are config-exposed. ocean_state%vmix%kv_max = cfg%ocean%vmix%kv_max ocean_state%vmix%kd_max = cfg%ocean%vmix%kd_max ocean_state%vmix%kd_smooth_iterations = cfg%ocean%vmix%kd_smooth_iterations ocean_state%vmix%vmix_guard = cfg%ocean%vmix%vmix_guard ! C7 Bryan-Lewis depth-varying background (default off = bit-identical). ocean_state%vmix%bkgnd_profile = cfg%ocean%vmix%bkgnd_profile ocean_state%vmix%bkgnd_kd_sfc = cfg%ocean%vmix%bkgnd_kd_sfc ocean_state%vmix%bkgnd_kd_deep = cfg%ocean%vmix%bkgnd_kd_deep ocean_state%vmix%bkgnd_z0 = cfg%ocean%vmix%bkgnd_z0 ocean_state%vmix%bkgnd_delta = cfg%ocean%vmix%bkgnd_delta ocean_state%vmix%bkgnd_prandtl = cfg%ocean%vmix%bkgnd_prandtl ocean_state%vmix%bkgnd_henyey = cfg%ocean%vmix%bkgnd_henyey ocean_state%vmix%bkgnd_kd_min = cfg%ocean%vmix%bkgnd_kd_min ocean_state%vmix%bkgnd_henyey_n0_2omega = cfg%ocean%vmix%bkgnd_henyey_n0_2omega ocean_state%vmix%bkgnd_henyey_max_lat = cfg%ocean%vmix%bkgnd_henyey_max_lat ! Backstop for the two `validate_config` guards (Bryan-Lewis and Henyey ! mutually exclusive; non-cartesian grid_config required) — reached only ! by a caller that bypassed validation, since the namelist path stops ! there first. Repeated here because a Henyey run with either ! precondition violated is silent (wrong background field), not noisy. if (bkgnd_henyey_conflicts_profile(cfg%ocean%vmix%bkgnd_henyey, & cfg%ocean%vmix%bkgnd_profile)) then call fail("ocean_vmix_nml: bkgnd_henyey and bkgnd_profile are "// & "mutually exclusive background schemes — the Henyey factor "// & "scales the SCALAR background, it does not compose with the "// & "Bryan-Lewis depth profile", ierr, OCEAN_STATUS_ERR_SETUP) return end if if (cfg%ocean%vmix%bkgnd_henyey .and. & trim(cfg%ocean%grid%grid_config) == "cartesian") then call fail("ocean_vmix_nml: bkgnd_henyey requires a non-cartesian "// & "grid_config — geolatT is identically zero on cartesian, so "// & "every column would take the equatorial L(0 deg)=0 and the "// & "background would flatten to a uniform bkgnd_kd_min", ierr, OCEAN_STATUS_ERR_SETUP) return end if if (compute_rank == 0) then if (cfg%ocean%vmix%bkgnd_profile) then call logger%info("Background mixing: Bryan-Lewis profile (Kd_sfc="// & to_string(cfg%ocean%vmix%bkgnd_kd_sfc)//" Kd_deep="// & to_string(cfg%ocean%vmix%bkgnd_kd_deep)//" z0="// & to_string(cfg%ocean%vmix%bkgnd_z0)//" m)") end if if (cfg%ocean%vmix%bkgnd_henyey) then call logger%info("Background mixing: Henyey IGW latitude factor on the "// & "scalar background (N0_2Omega="// & to_string(cfg%ocean%vmix%bkgnd_henyey_n0_2omega)// & " max_lat="//to_string(cfg%ocean%vmix%bkgnd_henyey_max_lat)// & " degN Kd_min="// & to_string(vmix_resolve_kd_min(cfg%ocean%vmix%bkgnd_kd_min, & cfg%ocean%vmix%pp81_kappa_bg))// & " m^2/s)") ! `pp81_kappa_bg` (not `ocean_state%vmix%kt_bg`) because ! `seed_backgrounds` — which re-derives kt_bg from it — runs ! later in this same routine; reading the slot here would log ! the stale type default whenever the user retuned kappa_bg. end if end if ! smooth_scratch is allocated ONLY when smoothing is requested — ! here, after kd_smooth_iterations is known, before enter_data. ! The enter_data path guards on allocated() so the not-allocated ! case (smoothing off) is handled correctly. if (cfg%ocean%vmix%kd_smooth_iterations > 0) then if (.not. allocated(ocean_state%vmix%smooth_scratch)) then associate (kv => ocean_state%vmix%kv) allocate (ocean_state%vmix%smooth_scratch( & size(kv, 1), size(kv, 2), size(kv, 3)), source=0.0_wp) end associate end if end if if (compute_rank == 0) then if (cfg%ocean%vmix%kd_max < huge(1.0_wp) .or. cfg%ocean%vmix%kv_max < huge(1.0_wp)) then call logger%info("Vmix assembly: kv_max="//to_string(cfg%ocean%vmix%kv_max)// & " kd_max="//to_string(cfg%ocean%vmix%kd_max)//" m²/s") end if if (cfg%ocean%vmix%kd_smooth_iterations > 0) then call logger%info("Vmix assembly: "// & to_string(cfg%ocean%vmix%kd_smooth_iterations)// & " 1-2-1 smoothing pass(es) on kv/kt (wet-mask aware)") end if if (cfg%ocean%vmix%vmix_guard) then call logger%info("Vmix assembly: negative/NaN guard ON") end if end if ! HARMONIC_VISC — vdiff face-thickness mean (better at thin layers). ocean_state%vdiff%use_harmonic = cfg%ocean%vmix%harmonic_visc ! MOM6 HARMONIC_VISC parity for the momentum face thickness (hvel) + ! arithmetic h_shear. Off by default => historical arithmetic h_u. ocean_state%vdiff%hvel_mom6 = cfg%ocean%vdiff%hvel_mom6 ocean_state%vdiff%hbbl_visc = cfg%ocean%vdiff%hbbl_visc ocean_state%vdiff%hvel_harmonic = cfg%ocean%vdiff%hvel_harmonic ! MOM6 bottomdraglaw coupling parity: kv_bbl botfn glue + piston bed ! drag (PGF_BUG.md §9), with MOM6's per-face `set_viscous_BBL` ! (`vdiff_set_viscous_bbl`) built from the bottom-drag configuration: ! the drag law, `cd`/`r`, `bg_vel`, `bbl_thick_min`, and `HBBL` = ! `&ocean_bdrag_nml hbbl`, falling back to `&ocean_vdiff_nml ! hbbl_visc` for a drag configured bed-only (`hbbl = 0`) — MOM6 has ! ONE HBBL and no bed-only form. No drag configured ⇒ the latch ! leaves the glue OFF. `hvel_mom6` is validated at configure. ocean_state%vdiff%bbl_glue = cfg%ocean%vdiff%bbl_glue ocean_state%vdiff%bbl_piston = cfg%ocean%vdiff%bbl_piston ocean_state%vdiff%hvel_upwind = cfg%ocean%vdiff%hvel_upwind block integer :: bbl_form real(wp) :: bbl_hbbl bbl_form = BBL_FORM_QUADRATIC if (trim(cfg%ocean%bdrag%form) == "linear") bbl_form = BBL_FORM_LINEAR bbl_hbbl = cfg%ocean%bdrag%hbbl if (bbl_hbbl <= 0.0_wp) bbl_hbbl = cfg%ocean%vdiff%hbbl_visc call vdiff_bbl_configure(ocean_state%vdiff, & size(ocean_state%multilayer%h_layer, 1), & size(ocean_state%multilayer%h_layer, 2), & size(ocean_state%multilayer%h_layer, 3), & bbl_form, cfg%ocean%bdrag%cd, cfg%ocean%bdrag%r, & bbl_hbbl, cfg%ocean%bdrag%bg_vel, & cfg%ocean%bdrag%bbl_thick_min, & cfg%ocean%kshear%enable, ocean_state%eos%rho0, & cfg%ocean%vmix%pp81_nu_bg) end block if (compute_rank == 0 .and. cfg%ocean%vdiff%hvel_mom6 & .and. .not. cfg%ocean%vdiff%hvel_upwind) then call logger%info("vdiff: hvel near-bed upwind blend OFF (pure harmonic hvel)") end if if (compute_rank == 0 .and. cfg%ocean%vdiff%hvel_mom6) then if (cfg%ocean%vdiff%hvel_harmonic) then call logger%info("vdiff: MOM6 hvel, HARMONIC_VISC=True branch (harmonic + "// & "near-bed upwind blend, arithmetic h_shear) ON") else call logger%info("vdiff: MOM6 hvel, HARMONIC_VISC=False branch (arithmetic, "// & "z_clear near-bed harmonic blend, arithmetic h_shear) ON") end if end if if (compute_rank == 0 .and. ocean_state%vdiff%bbl_glue) then call logger%info("vdiff: MOM6 bottomdraglaw BBL glue ON (per-face "// & "set_viscous_BBL kv_bbl/bbl_thick, "//trim(cfg%ocean%bdrag%form)// & " law; botfn interfaces + piston bed row replace the "// & "explicit bed-drag apply)") else if (compute_rank == 0 .and. cfg%ocean%vdiff%bbl_glue) then call logger%info("vdiff: bbl_glue requested but no bottom drag is configured "// & "(&ocean_bdrag_nml cd/r = 0, or linear with bg_vel = 0) — glue OFF") end if if (compute_rank == 0) then if (cfg%ocean%vmix%harmonic_visc) then call logger%info("Vertical visc: harmonic-mean face thickness") end if end if ! Implicit stress/drag folding into the vdiff tridiagonal ! (`&ocean_vdiff_nml`). When on, the corresponding explicit ! pre-solve apply is gated off in run_stage (see rdb_ocean_dyn), and ! the bottom-drag slot fills the bed-layer Rayleigh-rate field ! `lambda_bot_u/v` that the vdiff diagonal consumes. Mutual ! exclusions validated at configure (validate_config). ocean_state%vdiff%implicit_stress = cfg%ocean%vdiff%implicit_stress ocean_state%vdiff%implicit_drag = cfg%ocean%vdiff%implicit_drag ocean_state%bdrag%implicit_fold = cfg%ocean%vdiff%implicit_drag if (compute_rank == 0) then if (cfg%ocean%vdiff%implicit_stress) then call logger%info("Vertical visc: wind stress folded into vdiff "// & "surface (k=nz) RHS (implicit_stress)") end if if (cfg%ocean%vdiff%implicit_drag) then call logger%info("Vertical visc: bottom drag folded into vdiff "// & "bed (k=1) diagonal (implicit_drag)") end if end if ! DT_THERM ratio — thermo/tracer kernels fire every Nth outer step. ocean_state%dyn%dt_therm_ratio = max(1, cfg%ocean%vmix%dt_therm_ratio) if (compute_rank == 0) then if (ocean_state%dyn%dt_therm_ratio > 1) then call logger%info("Thermo ratio: "// & to_string(ocean_state%dyn%dt_therm_ratio)// & "·dt (DT_THERM = ratio · dt_dynamic)") end if end if ! DT_TRACER_ADVECT ratio — horizontal tracer advect fires every ! Nth outer step over the accumulated face transports. Must be ! >= 1 and dt_therm_ratio must be an integer multiple of it so the ! ALE remap (which fires at DT_THERM cadence) never lands inside an ! open accumulation window. Fail loud, host-side. if (cfg%ocean%vmix%dt_tracer_advect_ratio < 1) then call fail("ocean_vmix_nml: dt_tracer_advect_ratio must be >= 1 (got "// & to_string(cfg%ocean%vmix%dt_tracer_advect_ratio)//")", ierr, OCEAN_STATUS_ERR_SETUP) return end if ! `< 1` already error-stopped above, so the value is >= 1 here. ocean_state%dyn%dt_tracer_advect_ratio = cfg%ocean%vmix%dt_tracer_advect_ratio if (.not. ocean_dt_tracer_advect_ratios_ok(ocean_state%dyn%dt_therm_ratio, & ocean_state%dyn%dt_tracer_advect_ratio)) then call fail("ocean_vmix_nml: dt_therm_ratio ("// & to_string(ocean_state%dyn%dt_therm_ratio)// & ") must be an integer multiple of dt_tracer_advect_ratio ("// & to_string(ocean_state%dyn%dt_tracer_advect_ratio)// & ") so the ALE remap never fires mid-accumulation-window", ierr, OCEAN_STATUS_ERR_SETUP) return end if if (compute_rank == 0) then if (ocean_state%dyn%dt_tracer_advect_ratio > 1) then call logger%info("Tracer advect: "// & to_string(ocean_state%dyn%dt_tracer_advect_ratio)// & "·dt (DT_TRACER_ADVECT = ratio · dt_dynamic)") end if end if ! Q6: windowed-drain tracer face reconstruction (ppm | weno5/7/9). ! The per-rung nghost minimum is validated fail-loud in validate_config ! (cfg%nghost is the ocean grid nghost); here we only map the parsed ! code onto the continuity slot. ppm (default) ⇒ bit-identical. ocean_state%continuity%tracer_recon = & parse_tracer_recon(trim(cfg%ocean%vmix%tracer_recon)) if (compute_rank == 0 .and. & ocean_state%continuity%tracer_recon /= TRACER_RECON_PPM) then call logger%info("Tracer recon: "//trim(cfg%ocean%vmix%tracer_recon)// & " (windowed-drain WENO-Z swept-average face)") end if if (compute_rank == 0) then if (cfg%ocean%thermo%enable_thermodynamics) then call logger%info("Thermodynamics: on") else call logger%info("Thermodynamics: OFF (adiabatic — EOS/tracer kernels skipped)") end if end if if (present(ierr)) ierr = OCEAN_STATUS_OK end subroutine configure_ocean_vmix