Support NamedTuple-valued diagnostics#172
Conversation
A diagnostic whose `compute`/`compute!` returns a `Field` of `NamedTuple`s is now written as one scalar NetCDF variable per (possibly nested) leaf, named `<short_name>_<chain...>`. A single compute function (e.g. CloudMicrophysics debug-mode process tendencies) fans out into per-process diagnostics automatically. The split happens only at the writer boundary: storage, accumulators and reductions already broadcast per-leaf through ClimaCore's `RecursiveApply`. The remapper is scalar-only, so the NetCDF writer iterates `Fields.property_chains` and feeds zero-copy `single_field` views through the existing remap path. Triggered by `eltype(field) <: NamedTuple`, so scalar and `Geometry.AxisVector` diagnostics are unaffected. `HDF5Writer` and `DictWriter` store the field whole. `DiagnosticVariable(; units = ...)` now also accepts a `NamedTuple` (per-component) or a `Function(property_chain)`; a plain `String` is applied to every component (unchanged). Misspecified components emit a one-time warning at handler construction; an explicit `""` is silent. `component_units` guards on `key isa Symbol` so an `Int` chain element (a `Tuple`/`NTuple` index) cannot positional-index the units `NamedTuple`.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for diagnostics whose compute function returns a Field of NamedTuples, enabling automatic fan-out into one scalar NetCDF variable per component while keeping other writer paths unchanged.
Changes:
- Implement NetCDFWriter fan-out for
NamedTuple-valued fields (including nestedNamedTuples) with shared time-index handling across components. - Extend
DiagnosticVariable.unitsto support per-component resolution viaNamedTupleorproperty_chain -> unitsfunction, and propagate this to writers (NetCDF per-leaf; HDF5 flattened string). - Add docs + NEWS + version bump and comprehensive tests for NetCDF/HDF5/Dict behavior and end-to-end reductions.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/writers.jl | Adds integration-style tests for NetCDF fan-out (flat/nested), PointSpace behavior, and HDF5/Dict pass-through. |
| test/diagnostics.jl | Adds end-to-end handler-driven reduction test for NamedTuple-eltype diagnostics. |
| test/diagnostic_variable.jl | Adds unit-resolution tests for per-component units helpers and non-string units acceptance. |
| src/Writers.jl | Imports unit-resolution helpers for use by writer implementations. |
| src/netcdf_writer.jl | Implements NamedTuple component splitting, per-component metadata, and shared-time-index write logic. |
| src/hdf5_writer.jl | Writes variable_units via new flattened per-component units rendering. |
| src/DiagnosticVariables.jl | Broadens units type and introduces component_units, units_attribute, and units_warnings. |
| src/clima_diagnostics.jl | Emits one-time missing-units warnings for NamedTuple diagnostics; uses new PointSpace prealloc helper. |
| Project.toml | Bumps version to v0.3.6. |
| NEWS.md | Documents the new NamedTuple diagnostics feature. |
| docs/src/namedtuple_diagnostics.md | New user docs page explaining fan-out semantics, naming, and per-component units. |
| docs/make.jl | Adds the new docs page to the documentation navigation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| component_units(units::AbstractString, _) = units | ||
| component_units(units, property_chain) = units(property_chain) | ||
| function component_units(units::NamedTuple, property_chain) | ||
| isempty(property_chain) && return units |
| """ | ||
| units_attribute(units::AbstractString, _) = units | ||
| function units_attribute(units, property_chains) | ||
| labeled = map(property_chains) do chain | ||
| u = something(component_units(units, chain), "") | ||
| "$(join(string.(chain), '.')): $u" |
| `units` (and `long_name`) may now be specified per component, either as a | ||
| `NamedTuple` mapping each component to its units, or as a function | ||
| `property_chain -> units`. A plain `String` is still applied to every | ||
| component. |
| diagnostic, | ||
| var.short_name, | ||
| output_long_name(diagnostic), | ||
| string(var.units), |
|
I have only skimmed though this PR, but I do not think this functionality should be supported. This introduces extra complexity for future development of ClimaDiagnostics, since developers would need to worry about this case as well. There is also additional cost to maintain this as well. I am also not sure if the proposed solution to your problem is the best here. Also, this is going to break If the problem is hand writing N compute callbacks and N |
Purpose
Lets a compute function that returns a
FieldofNamedTuples be registered as a singleDiagnosticVariableand fan out into one scalar NetCDF variable per component, automatically. Motivated by an upcoming CloudMicrophysics debug mode in ClimaAtmos, which produces per-process tendencies as aNamedTupleat every grid point — previously this required N hand-writtencomputecallbacks (one per process) and NDiagnosticVariableregistrations to surface in NetCDF.Example
A
computereturning aField{@NamedTuple{cond, evap, accr}}:yields three NetCDF variables in a single file —
clw_tend_cond,clw_tend_evap,clw_tend_accr(withlong_name"cloud liquid water tendency (cond)", etc.). NestedNamedTuples flatten recursively:@NamedTuple{warm::@NamedTuple{au, ac}, tot}withshort_name = "proc"givesproc_warm_au,proc_warm_ac,proc_tot.Scalar diagnostics, reductions (incl. time averaging), and the
HDF5Writer/DictWriterpaths are unchanged. See the new NamedTuple diagnostics docs page for the full API.Content
NetCDFWriterboundary (scalar remapper reused, one sharedtimeindex per write)unitsacceptingString | NamedTuple | Function+reduction throughDiagnosticsHandler, units resolvers