ocean_sponge_refresh_target Subroutine

public 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.

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_sponge_t), intent(inout) :: sp
type(multilayer_state_t), intent(in) :: ms

Calls

proc~~ocean_sponge_refresh_target~~CallsGraph proc~ocean_sponge_refresh_target ocean_sponge_refresh_target proc~refresh_linear_z_impl refresh_linear_z_impl proc~ocean_sponge_refresh_target->proc~refresh_linear_z_impl local local proc~refresh_linear_z_impl->local

Called by

proc~~ocean_sponge_refresh_target~~CalledByGraph proc~ocean_sponge_refresh_target ocean_sponge_refresh_target proc~engine_step engine_step proc~engine_step->proc~ocean_sponge_refresh_target proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_step proc~rdb_ocean_step rdb_ocean_step proc~rdb_ocean_step->proc~engine_step proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean

Variables

Type Visibility Attributes Name Initial
integer, private :: nx
integer, private :: ny
integer, private :: nz

Source Code

   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