Auto-derive the barotropic substep count n_inner from the external
gravity-wave CFL (MOM6 set_dtbt) when requested, then latch the
mode-split reference column depth bt_H_ref from the seeded bathymetry.
cfg is intent(inout) because auto_n_inner writes cfg%ocean%bt%n_inner.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(config_t), | intent(inout) | :: | cfg | |||
| type(ocean_state_t), | intent(inout) | :: | ocean_state | |||
| type(hgrid_t), | intent(in) | :: | grid | |||
| integer, | intent(in) | :: | compute_rank |
subroutine configure_ocean_bt_split(cfg, ocean_state, grid, compute_rank) !! Auto-derive the barotropic substep count n_inner from the external !! gravity-wave CFL (MOM6 set_dtbt) when requested, then latch the !! mode-split reference column depth bt_H_ref from the seeded bathymetry. !! `cfg` is intent(inout) because auto_n_inner writes cfg%ocean%bt%n_inner. type(config_t), intent(inout) :: cfg type(ocean_state_t), intent(inout) :: ocean_state type(hgrid_t), intent(in) :: grid integer, intent(in) :: compute_rank ! Auto-derive n_inner from the external gravity-wave CFL (MOM6 set_dtbt), ! evaluated PER WET CELL: the local depth with the local 2-D CFL length ! `l = 1/sqrt(1/dx^2+1/dy^2)` (the cross-direction term matters — on ! square cells it is the sqrt(2) the legacy 1-D estimate omitted), over ! ocean only. See `bt_cfl_dt_wet` for why the old "deepest anywhere x ! smallest anywhere, land included" combination was wrong on a real ! global grid, and why this reproduces it bit-for-bit wherever the two ! extremes coincide on a wet cell. ! ! `multilayer%wet_mask`, not `metrics%wet_T`: the metric mask is built ! later, by `configure_ocean_land_mask`; its interior IS this array. if (cfg%ocean%bt%auto_n_inner) then block integer :: ng_, n_inner_derived, n_wet_local real(wp) :: dt_bt_local, dt_bt_safe, h_at, l_at ng_ = grid%nghost call bt_cfl_dt_wet(size(ocean_state%barotropic%b, 1), & size(ocean_state%barotropic%b, 2), & ng_ + 1, ng_ + grid%nx_phys, & ng_ + 1, ng_ + grid%ny_phys, & ocean_state%barotropic%b, & ocean_state%multilayer%wet_mask, & ocean_state%metrics%dxT, ocean_state%metrics%dyT, & cfg%ocean%bt%cfl_bt_safety, & dt_bt_local, h_at, l_at, n_wet_local) ! Reduce to the GLOBAL per-point minimum so every rank derives the ! SAME n_inner. Different n_inner per rank means a different ! number of barotropic substeps, which desyncs the per-substep ! grouped halo exchanges (Isend/Irecv pairing crosses between ! substeps -> MPI_ERR_TRUNCATE at the first N/S exchange; seen on ! a spherical py>1 split). A rank with no wet cell contributes ! `huge`, the identity of min. Single rank: identity stub. call halo_allreduce_min(dt_bt_local, dt_bt_safe) n_inner_derived = bt_auto_n_inner_from_dt(cfg%dt_fixed, dt_bt_safe) if (compute_rank == 0) then if (dt_bt_safe >= huge(1.0_wp)) then call logger%warning("Auto n_inner: no wet cell anywhere — "// & "n_inner = 1 (was "// & to_string(cfg%ocean%bt%n_inner)//")") else if (dt_bt_local == dt_bt_safe) then call logger%info("Auto n_inner (per wet cell): limiting cell H = "// & to_string(h_at)//" m, l_cfl = "// & to_string(l_at)//" m, c_ext = "// & to_string(sqrt(GRAVITY*max(h_at, 1.0_wp)))// & " m/s, dt_bt = "//to_string(dt_bt_safe)// & " s → n_inner = "//to_string(n_inner_derived)// & " (was "//to_string(cfg%ocean%bt%n_inner)//")") else call logger%info("Auto n_inner (per wet cell): dt_bt = "// & to_string(dt_bt_safe)//" s (limited on another "// & "rank) → n_inner = "//to_string(n_inner_derived)// & " (was "//to_string(cfg%ocean%bt%n_inner)//")") end if end if cfg%ocean%bt%n_inner = n_inner_derived end block end if ! Mode-split contract: bt_H_ref is the reference WATER-COLUMN ! thickness. Without an ice shelf that is the seeded bathymetry ! (Σh_layer = b); under one it is `b − z_draft`, which is what makes ! `bt_eta = Σh_layer − bt_H_ref` the deviation from the LOADED ! equilibrium (zero at rest) rather than a permanent −z_draft. That ! is Losch (2008) §2.1's own convention, and it is why every ! consumer of `D = bt_H_ref + bt_eta` — the BT continuity face ! thickness, the Chapman phase speed, the ALE `remap_h_ref` — needs ! no cavity branch of its own. Knob off ⇒ `z_draft ≡ 0` ⇒ the ! literal `bt_H_ref = b` this always was. ! ! The snapshot is taken from the UNWRAPPED `b`/`z_draft`; the engine ! re-wraps + halo-exchanges `bt_H_ref` right after, alongside both ! of its sources. ! ! NOTE on `auto_n_inner` above: it derives each wet cell's external ! gravity-wave speed from `b`, the BED depth, not from `b − z_draft`. ! Under a shelf that OVERESTIMATES `c_ext` and so buys more ! barotropic substeps than the CFL needs — conservative, never ! unstable, and at a calving front (where the draft is 0) it is ! exactly right. Left as-is deliberately: tightening it would ! change `n_inner` for cavity runs only, which is a separate, ! answer-changing decision. if (cfg%ocean%bt%n_inner >= 1) then if (ocean_state%metrics%use_cavity) then ! `cavity_datum_impl`, not the raw `b - z_draft`: a GROUNDED ! column has no water column, and its datum is 0 rather than ! a negative thickness. See that routine for why — in short, ! it is land, nothing downstream reads its datum, and the ! negative value put a phantom few-hundred-metre `bt_eta` on ! it and turned the ALE land target into a cancellation. call cavity_datum_impl(ocean_state%dyn%bt_work%bt_H_ref, & ocean_state%barotropic%b, & ocean_state%metrics%z_draft, & cfg%ocean%cavity_dyn%h_min_cavity, & size(ocean_state%barotropic%b, 1), & size(ocean_state%barotropic%b, 2)) else ocean_state%dyn%bt_work%bt_H_ref = ocean_state%barotropic%b end if end if end subroutine configure_ocean_bt_split