diag_var_t Derived Type

type, public :: diag_var_t

Per-variable diagnostic record. Owned by ocean_diag_t.


Inherits

type~~diag_var_t~~InheritsGraph type~diag_var_t diag_var_t type~diag_mask_t diag_mask_t type~diag_var_t->type~diag_mask_t mask

Inherited by

type~~diag_var_t~~InheritedByGraph type~diag_var_t diag_var_t type~ocean_diag_t ocean_diag_t type~ocean_diag_t->type~diag_var_t vars type~ocean_state_t ocean_state_t type~ocean_state_t->type~ocean_diag_t diag type~ocean_engine_t ocean_engine_t type~ocean_engine_t->type~ocean_state_t state type~ocean_handle_t ocean_handle_t type~ocean_handle_t->type~ocean_state_t state type~ocean_handle_t->type~ocean_engine_t engine

Components

Type Visibility Attributes Name Initial
real(kind=wp), public, allocatable :: accumulator(:,:,:)
real(kind=wp), public :: dt_accum = 0.0_wp

Wall time accumulated into this var’s window (s).

real(kind=wp), public :: dt_out = 3600.0_wp

Output cadence (s). Manager keeps a per-var counter and fires when the accumulated dt passes the threshold.

logical, public :: enabled = .true.

When .false. the dispatcher skips this var entirely — no fill_* kernel, no accumulator fold, no emit — so an unrequested diagnostic costs zero GPU work. Lets a run turn off individual default diagnostics it does not want. Default .true. ⇒ every registered var runs (bit-identical).

procedure(diag_fill_proc), public, pointer, nopass :: fill => null()
integer, public :: fire_count = 0

Number of times this var’s output_buffer has been refreshed (filled/folded/finalised + pulled host-ward) since registration. Incremented unconditionally in ocean_diag_step at the SAME point as the per-fire update self — independent of whether a NetCDF stream is open (nc_time_index stays 0 with diagnostics disabled or output suppressed, so it cannot serve this role). This is the generation the C ABI’s rdb_ocean_get_diagnostic_ptr returns: a Python Field re-checks it on access and knows output_buffer is already host-current the instant it changes (no separate refresh call needed — the pull above already happened synchronously).

logical, public :: has_missing = .false.

When .true. the remap fills target cells that overlap no water (below-bottom / pinched-out in a shallow column) with DIAG_MISSING_VALUE instead of 0, and the NetCDF writer tags the variable with a _FillValue / missing_value attribute. Set at register time for non-LAYER diagnostics when masking is enabled (&ocean_diag_nml mask_vanished_layers). Default .false. => below-bottom cells read 0 (bit-identical to the legacy writer).

logical, public :: is_extensive = .false.

.false. (default): field is INTENSIVE — per-unit-thickness quantity like temperature, salinity, velocity, density. When remapped to a non-LAYER vgrid, the column value is weight-averaged across overlapping source layers.

.true.: field is EXTENSIVE — already thickness-integrated, e.g. hTr (tracer · m), KE per layer (h · 0.5 · |u|²), transport per layer (h · u). When remapped, the column values must be conservatively REDISTRIBUTED across the target layers (sum preserved), not averaged.

Phase B v1 status: the existing remap_layer_to_z is intensive-only. Extensive remap (and the corresponding split inside diag_remap_proc) lands with isopycnal / density-bin remap in Phase E. Flag is here now so calling code can declare intent and the upgrade is non-breaking.

real(kind=wp), public, allocatable :: layer_buffer(:,:,:)
character(len=128), public :: long_name = ""

CF-compliant long_name attribute.

type(diag_mask_t), public, allocatable :: mask

Optional region mask. When allocated, fold_sample multiplies each sample by mask%weight(i, j) before folding into the accumulator — cells outside the region contribute zero. Output buffer shape is unchanged (full domain with zeros outside the mask); scalar-aggregating reductions land with the Phase D budget plumbing or as a Phase C v2 follow-on.

integer, public :: n_accum = 0

Number of contributions in the current accumulator window.

character(len=64), public :: name = ""

Short netcdf variable name.

integer, public :: nc_time_dimid = -1
integer, public :: nc_time_index = 0

Number of slices already written for this var (1-indexed position of the NEXT write).

integer, public :: nc_time_varid = -1
integer, public :: nc_varid = -1
integer, public :: nc_z_dimid = -1

-1 for 2D vars; set for 3D vars when the stream is opened.

real(kind=wp), public, allocatable :: output_buffer(:,:,:)
integer, public :: output_vgrid = DIAG_VGRID_LAYER
procedure(diag_remap_proc), public, pointer, nopass :: remap => null()

Optional vertical-remap routine. Set at register time when output_vgrid /= DIAG_VGRID_LAYER; null otherwise.

integer, public :: source_loc = DIAG_LOC_CENTER
integer, public :: source_vgrid = DIAG_VGRID_LAYER
character(len=64), public :: standard_name = ""
integer, public :: stream_id = 0

ID of the output stream this var ships to (Phase 6 wires the I/O server stream table).

integer, public :: time_op = DIAG_OP_MEAN
character(len=32), public :: units = ""

Type-Bound Procedures

procedure, public, non_overridable :: bytes => diag_var_bytes

  • private pure function diag_var_bytes(this) result(nbytes)

    Counted allocatable footprint of ONE registered diagnostic (0 for every buffer that is unallocated).

    Read more…

    Arguments

    Type IntentOptional Attributes Name
    class(diag_var_t), intent(in) :: this

    Return Value integer(kind=int64)

Source Code

   type :: diag_var_t
      !! Per-variable diagnostic record.  Owned by ocean_diag_t.
      character(len=64)  :: name = ""
         !! Short netcdf variable name.
      character(len=128) :: long_name = ""
         !! CF-compliant long_name attribute.
      character(len=32)  :: units = ""
      character(len=64)  :: standard_name = ""

      ! ---- Source binding ----
      ! Procedure pointer to the per-var fill routine.  Set at
      ! `register` time by the slot that owns the source data; the
      ! manager invokes it on cadence-fire to populate the buffer.
      ! `nopass` because the registry holds the bind, not the var.
      procedure(diag_fill_proc), pointer, nopass :: fill => null()
      procedure(diag_remap_proc), pointer, nopass :: remap => null()
         !! Optional vertical-remap routine.  Set at register time
         !! when `output_vgrid /= DIAG_VGRID_LAYER`; null otherwise.
      integer :: source_loc = DIAG_LOC_CENTER
      integer :: source_vgrid = DIAG_VGRID_LAYER

      ! ---- Output binding ----
      integer  :: output_vgrid = DIAG_VGRID_LAYER
      integer  :: time_op = DIAG_OP_MEAN
      real(wp) :: dt_out = 3600.0_wp
         !! Output cadence (s).  Manager keeps a per-var counter and
         !! fires when the accumulated dt passes the threshold.

      ! ---- Buffers ----
      ! `output_buffer` is the post-remap buffer that gets shipped
      ! to the I/O server.  `layer_buffer` is the manager-owned
      ! native-grid scratch the `fill` routine writes into when a
      ! remap step is required; for `output_vgrid == LAYER` the
      ! fill writes directly to `output_buffer` and `layer_buffer`
      ! stays unallocated.  `accumulator` is the running-sum buffer
      ! used when `time_op /= DIAG_OP_INSTANT`.
      real(wp), allocatable :: output_buffer(:, :, :)
      real(wp), allocatable :: layer_buffer(:, :, :)
      real(wp), allocatable :: accumulator(:, :, :)
      integer :: n_accum = 0
         !! Number of contributions in the current accumulator window.
      real(wp) :: dt_accum = 0.0_wp
         !! Wall time accumulated into this var's window (s).

      ! ---- Region restriction ----
      type(diag_mask_t), allocatable :: mask
         !! Optional region mask.  When allocated, `fold_sample`
         !! multiplies each sample by `mask%weight(i, j)` before
         !! folding into the accumulator — cells outside the region
         !! contribute zero.  Output buffer shape is unchanged (full
         !! domain with zeros outside the mask); scalar-aggregating
         !! reductions land with the Phase D budget plumbing or as
         !! a Phase C v2 follow-on.

      ! ---- Vertical quantity kind ----
      logical :: is_extensive = .false.
         !! `.false.` (default): field is INTENSIVE — per-unit-thickness
         !! quantity like temperature, salinity, velocity, density.
         !! When remapped to a non-LAYER vgrid, the column value is
         !! weight-averaged across overlapping source layers.
         !!
         !! `.true.`: field is EXTENSIVE — already thickness-integrated,
         !! e.g. `hTr` (tracer · m), KE per layer (h · 0.5 · |u|²),
         !! transport per layer (h · u).  When remapped, the column
         !! values must be conservatively REDISTRIBUTED across the
         !! target layers (sum preserved), not averaged.
         !!
         !! Phase B v1 status: the existing `remap_layer_to_z` is
         !! intensive-only.  Extensive remap (and the corresponding
         !! split inside `diag_remap_proc`) lands with isopycnal /
         !! density-bin remap in Phase E.  Flag is here now so calling
         !! code can declare intent and the upgrade is non-breaking.

      ! ---- Enable gate ----
      logical :: enabled = .true.
         !! When `.false.` the dispatcher skips this var entirely — no
         !! `fill_*` kernel, no accumulator fold, no emit — so an
         !! unrequested diagnostic costs zero GPU work.  Lets a run turn
         !! off individual default diagnostics it does not want.  Default
         !! `.true.` ⇒ every registered var runs (bit-identical).

      ! ---- Vanished-target masking ----
      logical :: has_missing = .false.
         !! When `.true.` the remap fills target cells that overlap no water
         !! (below-bottom / pinched-out in a shallow column) with
         !! `DIAG_MISSING_VALUE` instead of 0, and the NetCDF writer tags the
         !! variable with a `_FillValue` / `missing_value` attribute.  Set at
         !! register time for non-LAYER diagnostics when masking is enabled
         !! (`&ocean_diag_nml mask_vanished_layers`).  Default `.false.` =>
         !! below-bottom cells read 0 (bit-identical to the legacy writer).

      ! ---- I/O server binding ----
      integer :: stream_id = 0
         !! ID of the output stream this var ships to (Phase 6 wires
         !! the I/O server stream table).

      ! ---- Per-var NetCDF state ----
      ! Populated by `rdb_ocean_diag_netcdf` when the stream is
      ! opened; consumed by the NetCDF emit hook on each fire.
      integer :: nc_varid = -1
      integer :: nc_time_dimid = -1
      integer :: nc_time_varid = -1
      integer :: nc_z_dimid = -1
         !! -1 for 2D vars; set for 3D vars when the stream is opened.
      integer :: nc_time_index = 0
         !! Number of slices already written for this var (1-indexed
         !! position of the NEXT write).

      ! ---- In-memory access generation (P7) ----
      integer :: fire_count = 0
         !! Number of times this var's `output_buffer` has been
         !! refreshed (filled/folded/finalised + pulled host-ward) since
         !! registration. Incremented unconditionally in `ocean_diag_step`
         !! at the SAME point as the per-fire `update self` — independent
         !! of whether a NetCDF stream is open (`nc_time_index` stays 0
         !! with diagnostics disabled or output suppressed, so it cannot
         !! serve this role). This is the `generation` the C ABI's
         !! `rdb_ocean_get_diagnostic_ptr` returns: a Python `Field`
         !! re-checks it on access and knows `output_buffer` is already
         !! host-current the instant it changes (no separate refresh call
         !! needed — the pull above already happened synchronously).
   contains
      procedure, non_overridable :: bytes => diag_var_bytes
   end type diag_var_t