diag_field_stats Subroutine

public pure subroutine diag_field_stats(buf, on_device, vmin, vmax, vmean, n_valid, n_total)

The [diag] console line’s min / max / mean for one diagnostic buffer, over the finite cells only.

A diagnostic buffer legitimately carries IEEE NaN as its “no water here” sentinel: fill_tracer_impl writes one into every land column and every dynamically vanished layer (ZSTAR_FULL bed layers pinched out below zstar_h_min), because 0 degC / 0 PSU are legal ocean values and must not be confused with missing data. Those cells are not data, so none of the three statistics may see them and the mean divides by n_valid, not by the array size.

What this replaced, and why it was wrong: a plain minval/maxval/sum over the whole buffer. Comparisons with NaN are FALSE, so minval/maxval silently skipped the sentinel cells while sum propagated them — emitting a self-contradictory min= 1.5E+01 max= 1.5E+01 mean= NaN for a run whose state was entirely healthy, and tripping the regression suite’s NaN gate on every masked configuration (island / coastline / vanishing-layer cases). Leaning on NaN-false comparisons is not portable either: nvfortran’s relaxed-FP default may lower an unguarded min/max to a NaN-blind select (see CLAUDE.md’s clamp-laundering gotcha).

n_valid == n_total (the overwhelmingly common case — no land, no vanished layer) takes the unmasked intrinsics, so the emitted numbers stay BIT-IDENTICAL to the pre-fix behaviour on every all-finite field. n_valid == 0 reports DIAG_MISSING_VALUE for all three rather than the reduction’s untouched +huge/-huge seeds or a zero that reads as a legal value.

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: buf(:,:,:)
logical, intent(in) :: on_device

.true. when buf is device-resident, so the fused device reduction is the correct (and only correct) reader — a host read of a mapped buffer under -gpu=mem:separate is stale.

real(kind=wp), intent(out) :: vmin
real(kind=wp), intent(out) :: vmax
real(kind=wp), intent(out) :: vmean
integer, intent(out) :: n_valid
integer, intent(out) :: n_total

Calls

proc~~diag_field_stats~~CallsGraph proc~diag_field_stats diag_field_stats proc~diag_reduce_stats diag_reduce_stats proc~diag_field_stats->proc~diag_reduce_stats reduce reduce proc~diag_reduce_stats->reduce

Called by

proc~~diag_field_stats~~CalledByGraph proc~diag_field_stats diag_field_stats proc~ocean_diag_step ocean_diag_t%ocean_diag_step proc~ocean_diag_step->proc~diag_field_stats proc~engine_step_finalize engine_step_finalize proc~engine_step_finalize->proc~ocean_diag_step proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_step_finalize proc~rdb_ocean_step rdb_ocean_step proc~rdb_ocean_step->proc~engine_step_finalize proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean

Variables

Type Visibility Attributes Name Initial
real(kind=wp), private :: vsum

Source Code

   pure subroutine diag_field_stats(buf, on_device, vmin, vmax, vmean, n_valid, n_total)
      !! The `[diag]` console line's min / max / mean for one diagnostic
      !! buffer, **over the finite cells only**.
      !!
      !! A diagnostic buffer legitimately carries IEEE NaN as its "no water
      !! here" sentinel: `fill_tracer_impl` writes one into every land
      !! column and every dynamically vanished layer (ZSTAR_FULL bed layers
      !! pinched out below `zstar_h_min`), because 0 degC / 0 PSU are legal
      !! ocean values and must not be confused with missing data.  Those
      !! cells are not data, so none of the three statistics may see them
      !! and the mean divides by `n_valid`, not by the array size.
      !!
      !! What this replaced, and why it was wrong: a plain
      !! `minval`/`maxval`/`sum` over the whole buffer.  Comparisons with
      !! NaN are FALSE, so `minval`/`maxval` silently skipped the sentinel
      !! cells while `sum` propagated them — emitting a self-contradictory
      !! `min= 1.5E+01  max= 1.5E+01  mean= NaN` for a run whose state was
      !! entirely healthy, and tripping the regression suite's NaN gate on
      !! every masked configuration (island / coastline / vanishing-layer
      !! cases).  Leaning on NaN-false comparisons is not portable either:
      !! nvfortran's relaxed-FP default may lower an unguarded `min`/`max`
      !! to a NaN-blind select (see CLAUDE.md's clamp-laundering gotcha).
      !!
      !! `n_valid == n_total` (the overwhelmingly common case — no land, no
      !! vanished layer) takes the unmasked intrinsics, so the emitted
      !! numbers stay BIT-IDENTICAL to the pre-fix behaviour on every
      !! all-finite field.  `n_valid == 0` reports `DIAG_MISSING_VALUE` for
      !! all three rather than the reduction's untouched `+huge`/`-huge`
      !! seeds or a zero that reads as a legal value.
      real(wp), intent(in) :: buf(:, :, :)  ! assumed-shape-ok: diag emit — cadence-bounded
      logical, intent(in) :: on_device
         !! `.true.` when `buf` is device-resident, so the fused device
         !! reduction is the correct (and only correct) reader — a host
         !! read of a mapped buffer under `-gpu=mem:separate` is stale.
      real(wp), intent(out) :: vmin, vmax, vmean
      integer, intent(out) :: n_valid, n_total
      real(wp) :: vsum

      n_total = size(buf)
      if (on_device) then
         call diag_reduce_stats(buf, size(buf, 1), size(buf, 2), size(buf, 3), &
                                vmin, vmax, vsum, n_valid)
      else
         n_valid = count(ieee_is_finite(buf))
         if (n_valid == n_total) then
            vmin = minval(buf)
            vmax = maxval(buf)
            vsum = sum(buf)
         else
            vmin = minval(buf, mask=ieee_is_finite(buf))
            vmax = maxval(buf, mask=ieee_is_finite(buf))
            vsum = sum(buf, mask=ieee_is_finite(buf))
         end if
      end if

      if (n_valid > 0) then
         vmean = vsum/real(n_valid, wp)
      else
         vmin = DIAG_MISSING_VALUE
         vmax = DIAG_MISSING_VALUE
         vmean = DIAG_MISSING_VALUE
      end if
   end subroutine diag_field_stats