Adding a Diagnostic Variable

How to define a new diagnostic variable in ClimaAtmos. For computing and saving the diagnostics that already exist, see Computing and saving diagnostics.

Diagnostic variables are represented in ClimaAtmos with a DiagnosticVariable struct. A DiagnosticVariable contains metadata about the variable and a function that computes it from the state.

Metadata

The metadata we currently support is short_name, long_name, standard_name, units, and comments. This metadata is relevant mainly to how the variable is output. Therefore, it is the responsibility of the output_writer (see ScheduledDiagnostic) to handle the metadata properly. The output_writers provided by ClimaAtmos use this metadata.

In ClimaAtmos, we follow the convention that:

  • short_name is the name used to identify the variable in the output files and in the file names. It is short, but descriptive. We identify diagnostics by their short name, so the diagnostics defined by ClimaAtmos must have unique short_names.

  • long_name: Name used to describe the variable in the output file as an attribute.

  • standard_name: Standard name, as in CF conventions.

  • units: Physical units of the variable.

  • comments: More verbose explanation of what the variable is, or comments related to how it is defined or computed.

In ClimaAtmos, we follow the CMIP6 MIP table for short names and long names where available. Standard names in the table are not used.

Compute function

The other piece of information needed to specify a DiagnosticVariable is a function compute. Schematically, a compute has to look like

function compute(state, cache, time)
    return _ # Calculations with the state and the cache
end

The function takes the state, cache, and time from the integrator and returns the value of the diagnostic variable.

In-place computation

You can alternatively provide a compute! function. compute! takes a fourth argument, out, which is used to avoid extra memory allocations.

function compute!(out, state, cache, time)
    if isnothing(out)
        return _ # Calculations with the state and the cache
    else
        out .= _ # Calculations with the state and the cache
    end
end

The first time compute! is called, out is nothing, and the function has to allocate memory and return its output. On all subsequent calls, out will be the pre-allocated area of memory, so the function has to write the new value in place.

If your diagnostic depends on the details of the model, we recommend using additional functions so that the correct one can be found through dispatch. The following example demonstrates this using the compute interface. For instance, if you want to compute relative humidity, which does not make sense for dry simulations, you should define the functions

function compute_relative_humidity(
    state, cache, time, microphysics_model::T,
) where {T}
    error("Cannot compute relative_humidity with microphysics_model = $T")
end

function compute_relative_humidity(
    state, cache, time, microphysics_model::MoistMicrophysics,
)
    tps = CAP.thermodynamics_params(cache.params)
    (; ᶜT, ᶜp, ᶜq_tot_nonneg, ᶜq_liq, ᶜq_ice) = cache.precomputed
    return @. lazy(
        TD.relative_humidity(tps, ᶜT, ᶜp, ᶜq_tot_nonneg, ᶜq_liq, ᶜq_ice),
    )
end

compute_relative_humidity(state, cache, time) =
    compute_relative_humidity(state, cache, time, cache.atmos.microphysics_model)

This will return the correct relative humidity and throw informative errors when it cannot be computed. We could specialize compute_relative_humidity further if the relative humidity were computed differently for EquilibriumMicrophysics0M and NonEquilibriumMicrophysics.

In ClimaAtmos, we define some helper functions to produce error messages, so the error fallback can be written as

compute_relative_humidity(state, cache, time, model) =
    error_diagnostic_variable("relative_humidity", model)

The ClimaAtmos DiagnosticVariables

ClimaAtmos comes with a collection of pre-defined DiagnosticVariables, indexed by their short_names. If you are extending ClimaAtmos and want to add a new diagnostic variable, look at the files we include in diagnostics/Diagnostics.jl. You can add more diagnostics in those files or add a new one. We provide the convenience function add_diagnostic_variable! to add new DiagnosticVariables. add_diagnostic_variable! takes the same arguments as the constructor for DiagnosticVariable, but also performs additional checks. Similarly, if you want to retrieve a diagnostic from ALL_DIAGNOSTICS, use the get_diagnostic_variable function.