Re-evaluate the ANALYTIC target_source="linear_z" reference on
the LIVE layer geometry: for every sponge cell (idamp_h > 0)
and every layer,
ref_tracer(i,j,k,idx_t) = lin_t_ref - lin_dt_dz*z_ctr(i,j,k)
ref_tracer(i,j,k,idx_s) = lin_s_ref - lin_ds_dz*z_ctr(i,j,k)
with z_ctr the layer-centre GEOPOTENTIAL DEPTH measured from
the z = 0 datum, i.e. z_top + sum_{k'>k} h(k') + h(k)/2, and
z_top = sp%z_top(i,j) the depth of the column top (0 in the
open ocean, the ice draft under a shelf). z in the profile is
positive UP, hence the minus signs — the &ocean_zinit_nml
source="linear" convention exactly.
Under sigma / ALE the layer centres MOVE: with the free surface, with the remap, and (in a cavity) with whatever the column does under the lid. A target sampled once at t = 0 is a target on the t = 0 layer positions, and every later step relaxes the live column toward a profile that is no longer the geopotential one that was asked for. For ISOMIP+ Ocean1/Ocean2 the far-field restoring IS the entire forcing of the experiment, so a target that quietly drifts with the coordinate changes the forcing rather than perturbing it. Re-evaluating costs one column pass over the sponge cells per outer step — the same loop extent the relaxation itself already walks, on a band that is a few percent of the domain — so “cheap and exact” beats “free and wrong”.
This is the one place divergence #3 in the module docstring (the reference is snapshotted once) does NOT apply.
CADENCE: called once per OUTER step from ocean_engine_step,
beside ocean_porous_refresh, before the dyn step — so the
target is built on the layer geometry the whole step relaxes
against. Per-stage would be a half-step more current and buy
nothing the vertical resolution can see.
NO-OP unless enable .and. is_init .and. target_source ==
"linear_z", so every other configuration is bit-identical.
THE ARITHMETIC IS DUPLICATED FROM rdb_ocean_z_init
(build_z_ctr + linear_in_z) rather than imported, for two
reasons that are not style: (1) rdb_ocean_z_init is compiled
ONLY under RDB_ENABLE_NETCDF=ON (it carries the z-level file
reader) while this module is compiled unconditionally, so a
use would break the NetCDF-off build; (2) build_z_ctr writes
a whole z_ctr(nz) column, which inside a device kernel is
per-thread scratch, whereas the recurrence fuses into the
surface-down sweep here for free. The convention is pinned
ACROSS the two modules by
test_ocean_zinit::sponge_linear_z_target_matches_the_zinit_seed,
which is a stronger guarantee than a shared symbol: it compares
the numbers.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(hgrid_t), | intent(in) | :: | grid | |||
| type(ocean_sponge_t), | intent(inout) | :: | sp | |||
| type(multilayer_state_t), | intent(in) | :: | ms |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | private | :: | nx | ||||
| integer, | private | :: | ny | ||||
| integer, | private | :: | nz |
pure subroutine ocean_sponge_refresh_target(grid, sp, ms) !! Re-evaluate the ANALYTIC `target_source="linear_z"` reference on !! the LIVE layer geometry: for every sponge cell (`idamp_h > 0`) !! and every layer, !! !! ref_tracer(i,j,k,idx_t) = lin_t_ref - lin_dt_dz*z_ctr(i,j,k) !! ref_tracer(i,j,k,idx_s) = lin_s_ref - lin_ds_dz*z_ctr(i,j,k) !! !! with `z_ctr` the layer-centre GEOPOTENTIAL DEPTH measured from !! the `z = 0` datum, i.e. `z_top + sum_{k'>k} h(k') + h(k)/2`, and !! `z_top = sp%z_top(i,j)` the depth of the column top (0 in the !! open ocean, the ice draft under a shelf). `z` in the profile is !! positive UP, hence the minus signs — the `&ocean_zinit_nml !! source="linear"` convention exactly. !! !! ### Why re-evaluated, not frozen at t = 0 !! !! Under sigma / ALE the layer centres MOVE: with the free surface, !! with the remap, and (in a cavity) with whatever the column does !! under the lid. A target sampled once at t = 0 is a target on the !! t = 0 layer positions, and every later step relaxes the live !! column toward a profile that is no longer the geopotential one !! that was asked for. For ISOMIP+ Ocean1/Ocean2 the far-field !! restoring IS the entire forcing of the experiment, so a target !! that quietly drifts with the coordinate changes the forcing !! rather than perturbing it. Re-evaluating costs one column pass !! over the sponge cells per outer step — the same loop extent the !! relaxation itself already walks, on a band that is a few percent !! of the domain — so "cheap and exact" beats "free and wrong". !! !! This is the one place divergence #3 in the module docstring (the !! reference is snapshotted once) does NOT apply. !! !! CADENCE: called once per OUTER step from `ocean_engine_step`, !! beside `ocean_porous_refresh`, before the dyn step — so the !! target is built on the layer geometry the whole step relaxes !! against. Per-stage would be a half-step more current and buy !! nothing the vertical resolution can see. !! !! NO-OP unless `enable .and. is_init .and. target_source == !! "linear_z"`, so every other configuration is bit-identical. !! !! THE ARITHMETIC IS DUPLICATED FROM `rdb_ocean_z_init` !! (`build_z_ctr` + `linear_in_z`) rather than imported, for two !! reasons that are not style: (1) `rdb_ocean_z_init` is compiled !! ONLY under `RDB_ENABLE_NETCDF=ON` (it carries the z-level file !! reader) while this module is compiled unconditionally, so a !! `use` would break the NetCDF-off build; (2) `build_z_ctr` writes !! a whole `z_ctr(nz)` column, which inside a device kernel is !! per-thread scratch, whereas the recurrence fuses into the !! surface-down sweep here for free. The convention is pinned !! ACROSS the two modules by !! `test_ocean_zinit::sponge_linear_z_target_matches_the_zinit_seed`, !! which is a stronger guarantee than a shared symbol: it compares !! the numbers. type(hgrid_t), intent(in) :: grid type(ocean_sponge_t), intent(inout) :: sp type(multilayer_state_t), intent(in) :: ms integer :: nx, ny, nz if (.not. sp%is_init .or. .not. sp%enable) return if (trim(sp%target_source) /= "linear_z") return nx = grid%nx_total ny = grid%ny_total nz = ms%nz_ml if (sp%idx_t > 0) then call refresh_linear_z_impl(sp%ref_tracer, ms%h_layer, sp%z_top, sp%idamp_h, & sp%idx_t, sp%n_tracers, sp%lin_t_ref, sp%lin_dt_dz, & nx, ny, nz) end if if (sp%idx_s > 0) then call refresh_linear_z_impl(sp%ref_tracer, ms%h_layer, sp%z_top, sp%idamp_h, & sp%idx_s, sp%n_tracers, sp%lin_s_ref, sp%lin_ds_dz, & nx, ny, nz) end if end subroutine ocean_sponge_refresh_target