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.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | buf(:,:,:) | |||
| logical, | intent(in) | :: | on_device |
|
||
| 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 |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| real(kind=wp), | private | :: | vsum |
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