Bundle the per-stage vmix closure / KPP overlay / KV_ML_INVZ2 /
assembly gate / vdiff dispatch into one routine so the run_stage
drivers can call vmix_apply_in_stage(grid, dyn, vmix, vd, ss,
ms, dt, sf) instead of carrying 30 lines of nested if-branching.
Closure chain (all upstream CONTRIBUTORS into kv/kt): PP81 interior → KPP overlay XOR EPBL merge → kappa-shear additive merge → tidal-mixing additive merge → KV_ML_INVZ2 surface band → convective adjustment (Brunt-Vaisala trigger, interior-only, masked below the active KPP/EPBL boundary layer) → vmix_split_kd_heat_salt (derives ks from kt; NOT a contributor, must stay last) → vmix_assemble (the single downstream gate: background floors, kv_max/kd_max ceilings, optional smoothing, optional guard — applies to kv, kt, AND ks) → vdiff.
Logic preserved verbatim from the prior in-driver dispatch:
- When vmix%use_closure is true: optional PP81 / KPP
overlay populate vmix%kv/vmix%kt, then vdiff reads
them; KPP non-local γ is applied after the tracer vdiff
solve when thermodynamics are active.
- When vmix%use_closure is false: vdiff uses its scalar
K_v_* defaults — kv_source not passed.
- Tracer vdiff + KPP non-local fire only when
enable_thermodynamics .and. is_thermo_step().
Profiler regions are emitted from here uniformly (the prior run_stage path had no profiler regions around vmix; the split path did — now both share the same labels).
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_dyn_t), | intent(in) | :: | dyn | |||
| type(ocean_vmix_t), | intent(inout) | :: | vmix | |||
| type(ocean_vdiff_t), | intent(inout) | :: | vd | |||
| type(ocean_surface_stress_t), | intent(in) | :: | ss | |||
| type(ocean_bottom_drag_t), | intent(in) | :: | bd |
Bottom-drag slot — supplies the bed-layer Rayleigh-rate field
|
||
| type(multilayer_state_t), | intent(inout) | :: | ms | |||
| real(kind=wp), | intent(in) | :: | dt | |||
| integer, | intent(in) | :: | stage |
RK2 stage (1 or 2): the EPBL/kappa-shear COLUMN SOLVES fire only at stage 1 of a thermo step (their kd is stage-invariant by design — recomputing at stage 2 doubled the closure cost for no accuracy: 19.6%% of GPU time was kappa-shear at 2x cadence). The merges still run EVERY stage (PP81 rewrites kv/kt per stage). |
||
| type(ocean_surface_flux_t), | intent(in), | optional | :: | sf | ||
| type(ocean_epbl_t), | intent(inout), | optional | :: | epbl |
EPBL slot. When present and enabled, replaces the KPP
overlay (configure enforces the mutual exclusion):
|
|
| type(ocean_kappa_shear_t), | intent(inout), | optional | :: | kshear |
Kappa-shear interior closure slot. When present and enabled,
|
|
| type(ocean_tidal_mixing_t), | intent(inout), | optional | :: | vmix_tidal |
St-Laurent/Simmons tidal-mixing interior closure slot. When
present and enabled, |
|
| type(barotropic_workstate_t), | intent(inout), | optional | :: | bt_work |
The BT-corrector workstate — supplies |
|
| 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) |
Ice-shelf top-drag Rayleigh rate (1/s) at u / v faces — the
EXPLICIT SHAPE, not |
|
| 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) |
Face ice-cover masks (the OR of the two abutting cells).
Present together with |
|
| logical, | intent(in), | optional | :: | apply_tracers |
|
|
| type(ocean_metrics_t), | intent(in), | optional | :: | metrics |
Metrics slot — supplies the halo-valid wet masks
( |
|
| real(kind=wp), | intent(in), | optional | :: | dt_remnant |
|
|
| type(ocean_bc_state_t), | intent(in), | optional | :: | bc |
Open-boundary / periodic / tripolar-fold state — forwarded
ONLY so the visc_rem halo refresh ( |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| logical, | private | :: | do_remnant | ||||
| logical, | private | :: | do_tracers | ||||
| logical, | private | :: | epbl_active | ||||
| logical, | private | :: | kshear_active | ||||
| logical, | private | :: | request_remnant | ||||
| logical, | private | :: | split_remnant | ||||
| logical, | private | :: | tidal_active | ||||
| logical, | private | :: | vertex_kv |
subroutine vmix_apply_in_stage(grid, dyn, vmix, vd, ss, bd, ms, dt, stage, sf, epbl, kshear, vmix_tidal, bt_work, & lambda_top_u, lambda_top_v, cover_u, cover_v, & apply_tracers, metrics, dt_remnant, bc) !! Bundle the per-stage vmix closure / KPP overlay / KV_ML_INVZ2 / !! assembly gate / vdiff dispatch into one routine so the run_stage !! drivers can call `vmix_apply_in_stage(grid, dyn, vmix, vd, ss, !! ms, dt, sf)` instead of carrying 30 lines of nested if-branching. !! !! Closure chain (all upstream CONTRIBUTORS into kv/kt): !! PP81 interior → KPP overlay XOR EPBL merge → kappa-shear !! additive merge → tidal-mixing additive merge → KV_ML_INVZ2 !! surface band → convective adjustment (Brunt-Vaisala trigger, !! interior-only, masked below the active KPP/EPBL boundary !! layer) → **vmix_split_kd_heat_salt** (derives ks from kt; NOT a !! contributor, must stay last) → **vmix_assemble** (the single !! downstream gate: background floors, kv_max/kd_max ceilings, !! optional smoothing, optional guard — applies to kv, kt, AND ks) !! → vdiff. !! !! Logic preserved verbatim from the prior in-driver dispatch: !! - When `vmix%use_closure` is true: optional PP81 / KPP !! overlay populate `vmix%kv`/`vmix%kt`, then vdiff reads !! them; KPP non-local γ is applied after the tracer vdiff !! solve when thermodynamics are active. !! - When `vmix%use_closure` is false: vdiff uses its scalar !! `K_v_*` defaults — kv_source not passed. !! - Tracer vdiff + KPP non-local fire only when !! `enable_thermodynamics .and. is_thermo_step()`. !! !! Profiler regions are emitted from here uniformly (the prior !! run_stage path had no profiler regions around vmix; the !! split path did — now both share the same labels). type(hgrid_t), intent(in) :: grid type(ocean_dyn_t), intent(in) :: dyn type(ocean_vmix_t), intent(inout) :: vmix type(ocean_vdiff_t), intent(inout) :: vd type(ocean_surface_stress_t), intent(in) :: ss type(ocean_bottom_drag_t), intent(in) :: bd !! Bottom-drag slot — supplies the bed-layer Rayleigh-rate field !! `lambda_bot_u/v` for the implicit-drag vdiff fold. type(multilayer_state_t), intent(inout) :: ms real(wp), intent(in) :: dt integer, intent(in) :: stage !! RK2 stage (1 or 2): the EPBL/kappa-shear COLUMN SOLVES fire !! only at stage 1 of a thermo step (their kd is !! stage-invariant by design — recomputing at stage 2 doubled !! the closure cost for no accuracy: 19.6%% of GPU time was !! kappa-shear at 2x cadence). The merges still run EVERY !! stage (PP81 rewrites kv/kt per stage). type(ocean_surface_flux_t), intent(in), optional :: sf type(ocean_epbl_t), intent(inout), optional :: epbl !! EPBL slot. When present and enabled, replaces the KPP !! overlay (configure enforces the mutual exclusion): !! `epbl_compute` refreshes `kd_int` at thermo cadence and !! the merge into `vmix%kv` / `vmix%kt` runs every stage !! (PP81 rewrites those arrays each stage). type(ocean_kappa_shear_t), intent(inout), optional :: kshear !! Kappa-shear interior closure slot. When present and enabled, !! `kappa_shear_compute` refreshes `kd_int` at thermo cadence and !! the additive merge into `vmix%kv` / `vmix%kt` runs every stage. !! Coexists with KPP / EPBL (no mutual exclusion). type(ocean_tidal_mixing_t), intent(inout), optional :: vmix_tidal !! St-Laurent/Simmons tidal-mixing interior closure slot. When !! present and enabled, `tidal_mixing_compute` refreshes `kd_int` !! at thermo cadence and the additive merge into `vmix%kv` / !! `vmix%kt` runs every stage. Coexists with KPP / EPBL / !! kappa-shear (no mutual exclusion). type(barotropic_workstate_t), intent(inout), optional :: bt_work !! The BT-corrector workstate — supplies `visc_rem_u/v` as the !! vdiff kernel's OUTPUT. Present only from `run_stage_split` !! (the unsplit path has no BT correction to consume it). When !! present AND `bt_work%bt_visc_rem_producer`, the viscous !! remnant γ is (re)computed here, at step 9 of the CURRENT !! stage — the next stage's forcing/renorm/bt_rem_from consumers !! read it, a one-stage (Δt/2) lag (see the step-9 call site !! below). Absent, or no consumer on, ⇒ no remnant work ⇒ !! bit-identical. real(wp), intent(in), optional :: lambda_top_u(grid%nx_total + 1, grid%ny_total) real(wp), intent(in), optional :: lambda_top_v(grid%nx_total, grid%ny_total + 1) !! Ice-shelf top-drag Rayleigh rate (1/s) at u / v faces — the !! `ocean_top_drag_t` slot's `lambda_top_u/v`, forwarded !! verbatim to `vdiff_apply_momentum`'s `k = nz` diagonal fold. !! Passed as ARRAYS rather than the slot itself because the slot !! is optional one level up: forwarding an absent optional !! ARRAY dummy on to another optional dummy is legal Fortran, !! whereas dereferencing an absent derived-type dummy is not. !! Absent, or `vd%implicit_top_drag` off ⇒ bit-identical. !! !! EXPLICIT SHAPE, not `(:, :)`: the attribute has to hold on !! EVERY frame that forwards the optional, or gfortran reinstates !! the speculative pack (and its uninitialised packing flag) in !! whichever frame still hands an assumed-shape actual down. The !! whole argument is written out on `vdiff_apply_momentum`. real(wp), intent(in), optional :: cover_u(grid%nx_total + 1, grid%ny_total) real(wp), intent(in), optional :: cover_v(grid%nx_total, grid%ny_total + 1) !! Face ice-cover masks (the OR of the two abutting cells). !! Present together with `lambda_top_*`; used to mask the wind !! RHS off on covered faces. Explicit-shape for the same reason. logical, intent(in), optional :: apply_tracers !! `.false.` = momentum-only: skip the tracer vdiff + KPP !! non-local applies regardless of the thermo gate. The pred_corr !! PREDICTOR passes this — MOM6's predictor applies !! `vertvisc(up, dt_pred)` to the provisional velocity (so the !! spurious grounded-layer accelerations are absorbed BEFORE !! continuity forms `u_av`) but never touches tracers. !! Default `.true.` = historical. type(ocean_metrics_t), intent(in), optional :: metrics !! Metrics slot — supplies the halo-valid wet masks !! (`wet_T`/`wet_u`/`wet_v`) the kappa-shear VERTEX form's !! corner gather needs, and `metrics%geolatT` — the C7 Henyey !! latitude factor's only spatial input, forwarded to !! `vmix_assemble` (unread when `bkgnd_henyey` is off). !! Optional so the split_rk2/legacy call shapes stay valid; both !! consumers fail loud when their knob is on and the slot is !! absent — `kappa_shear_compute` for `at_vertex`, and !! `vmix_assemble` for `bkgnd_henyey`. real(wp), intent(in), optional :: dt_remnant !! PR-1: when present AND different from the velocity-apply !! `dt` (the `pred_corr` PREDICTOR, where this routine is called !! with `dt_vel = pc_be·dt`), the visc_rem PRODUCER is split out !! of the velocity solve and re-run as its own remnant-only call !! at `dt_remnant` — matching MOM6's `VISC_REM_TIMESTEP_BUG = !! .false.` default (`vertvisc_remnant` always at the outer !! step's `dt`), never at !! `dt_pred`. Absent ⇒ the historical fused behaviour (remnant !! built from the SAME matrix as the velocity solve, at `dt`). !! See `bt_forcing_visc_rem`'s docstring in !! `rdb_barotropic_workstate` for the full call-point mapping. type(ocean_bc_state_t), intent(in), optional :: bc !! Open-boundary / periodic / tripolar-fold state — forwarded !! ONLY so the visc_rem halo refresh (`visc_rem_halo_refresh`) !! can re-wrap `bt_work%visc_rem_u/v`'s ghosts after production. !! Unread when `do_remnant` is false. logical :: epbl_active, kshear_active, tidal_active, do_remnant logical :: do_tracers, vertex_kv, split_remnant, request_remnant do_tracers = .true. if (present(apply_tracers)) do_tracers = apply_tracers epbl_active = .false. if (present(epbl)) epbl_active = epbl%enable kshear_active = .false. if (present(kshear)) kshear_active = kshear%enable ! Vertex kappa-shear routes its viscosity corner->face: the ! momentum vdiff gets `kd_corner` as `kv_corner_source` (scaled by ! prandtl_turb) and the cell-centred kv merge is suppressed inside ! `kappa_shear_merge_into_kv_kt` (MOM6 zeroes the tracer-point ! Kv_shear when VERTEX_SHEAR is on — no double-count). vertex_kv = .false. if (present(kshear)) vertex_kv = kshear_active .and. kshear%at_vertex tidal_active = .false. if (present(vmix_tidal)) tidal_active = vmix_tidal%enable ! D1 follow-up: the producer must run whenever ANY consumer needs ! visc_rem_u/v fresh, not only the (retired) weighted BT-correction ! fold — read the decoupled `bt_visc_rem_producer` gate (set by ! `configure_ocean_bt` to the OR of forcing/renorm/bt_rem_from and ! the legacy correction flag), not `bt_correction_visc_rem` alone. do_remnant = .false. if (present(bt_work)) do_remnant = bt_work%bt_visc_rem_producer ! PR-1 VISC_REM_TIMESTEP_BUG fix: at the pred_corr PREDICTOR this ! routine is called with `dt_vel = pc_be·dt` (the provisional ! velocity's own apply dt), but MOM6's default (non-buggy) remnant ! is always built at the OUTER step's `dt`. Since the remnant ! matrix depends only on {dt, h, kv, drag} — never on velocity — it ! cannot be produced correctly by fusing it into a dt_vel-based ! velocity solve; `split_remnant` routes it to a SEPARATE ! remnant-only call at `dt_remnant` instead (`visc_rem_precompute`), ! run AFTER the (remnant-free) velocity solve below. `request_remnant` ! is what actually reaches `vdiff_apply_momentum` this call. split_remnant = .false. if (do_remnant .and. present(dt_remnant)) split_remnant = (dt_remnant /= dt) request_remnant = do_remnant .and. .not. split_remnant if (vmix%use_closure) then call profiler_start("ocean_vmix_compute") if (vmix%interior_closure == VMIX_INTERIOR_PP81) then call vmix_compute_pp81(grid, vmix, ms) else ! PR-6 fail-loud: VMIX_INTERIOR_LARGE94 / VMIX_INTERIOR_CVMIX are ! reserved-but-unwired (no kernel). Selecting one would leave ! kv/kt with NO interior mixing. There is no namelist key for ! interior_closure today, so this is defence-in-depth for the ! next code/config consumer that sets it. call logger%error("vmix_apply_in_stage: the selected interior closure "// & "has no kernel (only VMIX_INTERIOR_PP81 is implemented; "// & "VMIX_INTERIOR_LARGE94/CVMIX are reserved-but-unwired) — "// & "running it would leave kv/kt with no interior mixing") error stop "vmix_apply_in_stage: unimplemented vmix interior closure" end if if (vmix%use_kpp .and. .not. epbl_active) then if (.not. present(sf)) then call logger%error("vmix_apply_in_stage: KPP requires the surface-flux slot (sf)") error stop "vmix_apply_in_stage: use_kpp requires the surface-flux slot (sf)" end if call vmix_apply_kpp_overlay(grid, vmix, ms, ss, sf) end if if (epbl_active) then if (.not. present(sf)) then call logger%error("vmix_apply_in_stage: EPBL requires the surface-flux slot (sf)") error stop "vmix_apply_in_stage: EPBL requires the surface-flux slot (sf)" end if if (dyn%enable_thermodynamics .and. dyn%is_thermo_step() .and. stage == 1) then call epbl_compute(grid, epbl, ms, ss, dyn%therm_dt(dt), sf) end if call epbl_merge_into_kv_kt(epbl, size(vmix%kt, 1), size(vmix%kt, 2), & size(vmix%kt, 3), vmix%kv, vmix%kt) end if if (kshear_active) then if (dyn%enable_thermodynamics .and. dyn%is_thermo_step() .and. stage == 1) then if (kshear%at_vertex) then ! Vertex form needs the halo-valid wet masks for the ! corner gather; fail-loud inside if metrics is absent. if (.not. present(metrics)) then call logger%error("vmix_apply_in_stage: kappa-shear at_vertex "// & "requires the metrics slot (wet masks)") error stop "vmix_apply_in_stage: at_vertex needs metrics" end if call kappa_shear_compute(grid, kshear, ms, dyn%therm_dt(dt), & wet_t=metrics%wet_T, wet_u=metrics%wet_u, & wet_v=metrics%wet_v) else call kappa_shear_compute(grid, kshear, ms, dyn%therm_dt(dt)) end if end if call kappa_shear_merge_into_kv_kt(kshear, size(vmix%kt, 1), size(vmix%kt, 2), & size(vmix%kt, 3), vmix%kv, vmix%kt) end if if (tidal_active) then if (dyn%enable_thermodynamics .and. dyn%is_thermo_step() .and. stage == 1) then call tidal_mixing_compute(grid, vmix_tidal, ms, dyn%therm_dt(dt)) end if call tidal_mixing_merge_into_kt(vmix_tidal, size(vmix%kt, 1), size(vmix%kt, 2), & size(vmix%kt, 3), vmix%kv, vmix%kt) end if call vmix_add_kv_ml_invz2(grid, vmix, ms) if (vmix%conv_enable) then ! Brunt-Vaisala-triggered convective adjustment. A CONTRIBUTOR ! (max() floor), so it must run BEFORE vmix_assemble -- see the ! rdb_ocean_vmix module docstring for the D1-D4 divergences from ! MOM6's MOM_CVMix_conv. Masks against the live BL depth of ! whichever surface scheme is active this stage. if (epbl_active) then call vmix_apply_convection(grid, vmix, ms, epbl%mld) else call vmix_apply_convection(grid, vmix, ms, vmix%bl_depth) end if end if ! `vmix_split_kd_heat_salt` is NOT a contributor -- it must be ! the LAST statement before `vmix_assemble`, always. Any future ! PR adding a kv/kt contributor (e.g. convective adjustment) ! inserts ABOVE this line, never below it -- ks is derived from ! whatever kt holds at this point, so a contributor placed after ! the split silently never reaches ks. call vmix_split_kd_heat_salt(grid, vmix, ms) ! Single downstream assembly gate: background floors, kv_max/kd_max ! ceilings, optional 1-2-1 smoothing, optional negative/NaN guard. ! Defaults reproduce the pre-assembly chain bit-for-bit. Gates kv, ! kt, AND ks. `geolat` rides on the optional metrics slot (the C7 ! Henyey latitude factor's only spatial input) — forwarded when the ! caller threaded metrics through, omitted otherwise. Omitting it ! is safe: vmix_assemble fails loud if `bkgnd_henyey` is on and ! geolat is absent, so a caller that forgot cannot silently lose ! the latitude factor. if (present(metrics)) then call vmix_assemble(grid, vmix, ms, geolat=metrics%geolatT) else call vmix_assemble(grid, vmix, ms) end if call profiler_stop("ocean_vmix_compute") call profiler_start("ocean_vdiff_apply") if (vertex_kv) then ! Corner Kv seam: same calls + the corner viscosity source. if (request_remnant) then call vdiff_apply_momentum(grid, vd, ms, dt, kv_source=vmix%kv, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & visc_rem_u=bt_work%visc_rem_u, visc_rem_v=bt_work%visc_rem_v, & kv_corner_source=kshear%kd_corner, & kv_corner_prandtl=kshear%prandtl_turb) else call vdiff_apply_momentum(grid, vd, ms, dt, kv_source=vmix%kv, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & kv_corner_source=kshear%kd_corner, & kv_corner_prandtl=kshear%prandtl_turb) end if else if (request_remnant) then call vdiff_apply_momentum(grid, vd, ms, dt, kv_source=vmix%kv, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & visc_rem_u=bt_work%visc_rem_u, visc_rem_v=bt_work%visc_rem_v) else call vdiff_apply_momentum(grid, vd, ms, dt, kv_source=vmix%kv, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v) end if if (do_tracers .and. dyn%enable_thermodynamics .and. dyn%is_thermo_step()) then call vdiff_apply_tracers(grid, vd, ms, dyn%therm_dt(dt), & kt_source=vmix%kt, ks_source=vmix%ks) if (vmix%use_kpp) then call vmix_apply_nonlocal_tendencies(grid, vmix, ms, dyn%therm_dt(dt)) end if end if call profiler_stop("ocean_vdiff_apply") else call profiler_start("ocean_vdiff_apply") if (request_remnant) then call vdiff_apply_momentum(grid, vd, ms, dt, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, & visc_rem_u=bt_work%visc_rem_u, visc_rem_v=bt_work%visc_rem_v) else call vdiff_apply_momentum(grid, vd, ms, dt, & tau_u=ss%tau_x, tau_v=ss%tau_y, & lambda_bot_u=bd%lambda_bot_u, & lambda_bot_v=bd%lambda_bot_v, rho0=ss%rho0, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v) end if if (do_tracers .and. dyn%enable_thermodynamics .and. dyn%is_thermo_step()) then call vdiff_apply_tracers(grid, vd, ms, dyn%therm_dt(dt)) end if call profiler_stop("ocean_vdiff_apply") end if ! PR-1: the split-dt remnant refresh (predictor stage, see ! `split_remnant` above) runs AFTER the velocity solve above, at ! `dt_remnant` — `visc_rem_precompute` does its own halo/periodic/ ! fold refresh at the end, so nothing further is needed here. The ! FUSED path (every other call site) must still get its own halo ! refresh — MOM6's `pass_visc_rem` group pass runs after EVERY ! `vertvisc_remnant` call, not just the split one. if (split_remnant) then call visc_rem_precompute(grid, bt_work, vmix, vd, ss, bd, ms, dt_remnant, kshear=kshear, & lambda_top_u=lambda_top_u, lambda_top_v=lambda_top_v, & cover_u=cover_u, cover_v=cover_v, bc=bc) else if (request_remnant) then call visc_rem_halo_refresh(grid, bt_work, bc) end if end subroutine vmix_apply_in_stage