I/O Module
Run
- class elm_diagnostics.io.run.Run(path, name=None, streams=None, chunks=None, chunk_mode='auto', chunk_target_mb=64, analysis_year=None, analysis_year_min=None, analysis_year_max=None, analysis_year_tolerance=0, strict_combine=False)[source]
Bases:
objectAtomic unit of analysis: one ELM case’s history-file streams.
- Parameters:
path (
str|Path) – Directory containing*.elm.h*.ncfiles, or a glob pattern.name (
str|None) – Display name. Defaults to the case name extracted from filenames.streams (
dict[str,str] |None) – Explicit stream mapping, e.g.{"h0": "*.elm.h0.*.nc"}. If None, streams are auto-discovered.chunks (
dict|None) – Passed toxr.open_mfdatasetfor dask-backed lazy loading.analysis_year (
int|None) – Requested analysis year for early file narrowing before open.analysis_year_min (
int|None) – Inclusive lower bound for year-aware file narrowing.analysis_year_max (
int|None) – Inclusive upper bound for year-aware file narrowing.analysis_year_tolerance (
int) – Year-window half-width when narrowing files (0 means exact year).strict_combine (
bool) – If True, open streams using stricter multi-file combine options. Defaults to False.chunk_mode (Literal['off', 'auto', 'manual'])
chunk_target_mb (int)
- bounds_dataset(tape=None)[source]
Return the stream dataset used for flux-integration time bounds.
Balance modules need
time_bounds(and thetimecoordinate) for cumulative integration. This returns the cached full stream for the tape that carries the bounds, opened once via_open_stream. With thecompat="override"combine strategy, that open is already cheap even for many-file, many-variable streams, so no separate bounds-only open is needed.
- get(varname, tape=None)[source]
Retrieve a variable, searching tapes in priority order.
If the variable is not found, attempts to derive it from available components (e.g., compute QFLX_EVAP_TOT from QSOIL + QVEGE + QVEGT).
Variables are cached with an LRU policy (max 15 variables) to balance performance and memory usage on large datasets.
The per-tape variable-name index (single file header) is used to route to the tape that owns
varnamewithout opening the other tapes’ datasets. The owning stream is then opened once (cached via_open_stream) and the variable sliced out; the open is cheap thanks to thecompat="override"combine strategy.
- has(varname)[source]
Check whether a variable exists in any tape or can be derived.
Uses the per-tape header index (single file read) rather than opening full streams, and checks derivability via component availability (
DERIVABLE_REQUIREMENTS) instead of executing the derivation. Both keep this cheap on high-variable-count datasets.
Comparison
Derived Variables
Compute derived ELM variables from available history output.
This module provides functions to compute commonly-needed variables that may not be in the default h0 output, but can be calculated from component variables.
Based on ELM source code analysis (April 2026): - Total ET from components: QSOIL + QVEGE + QVEGT - Total storage from vertical profiles: sum(SOILLIQ) + sum(SOILICE) + …
- elm_diagnostics.io.derived.compute_total_et(run)[source]
Compute total evapotranspiration if QFLX_EVAP_TOT is missing.
- Based on ELM source (SoilFluxesMod.F90, VegetationDataType.F90):
QFLX_EVAP_TOT = QSOIL + QVEGE + QVEGT
- Where:
QSOIL = Ground evaporation (soil/snow evap + sublimation - dew) QVEGE = Canopy evaporation (evap from leaves and stems) QVEGT = Canopy transpiration (stomatal)
- Parameters:
run (
Run) – Run object containing the necessary component variables.- Returns:
Total evapotranspiration in mm/s (or units of components).
- Return type:
- Raises:
ValueError – If required component variables are not available.
- elm_diagnostics.io.derived.aggregate_vertical_storage(run, varname, vertical_dim='levgrnd')[source]
Aggregate a vertical profile storage variable to column total.
For variables like SOILLIQ(time, levgrnd, …) or SOILICE(time, levgrnd, …), sum over the vertical dimension to get total column storage.
- Parameters:
- Returns:
Column-total storage, summed over vertical levels.
- Return type:
- elm_diagnostics.io.derived.compute_total_soil_water(run)[source]
Compute total soil water (liquid + ice) column storage.
Sums SOILLIQ and SOILICE over vertical levels.
- elm_diagnostics.io.derived.compute_total_precip(run)[source]
Compute total precipitation rate from liquid and frozen components.
- Based on ELM forcing conventions:
PRECT = RAIN + SNOW
- Where:
RAIN = liquid precipitation rate (forc_rain) SNOW = frozen precipitation rate (forc_snow)
- Parameters:
run (
Run) – Run object containing RAIN and SNOW history variables.- Returns:
Total precipitation rate in the same units as RAIN (typically mm/s).
- Return type:
- Raises:
ValueError – If either RAIN or SNOW is not available in the run.
- elm_diagnostics.io.derived.can_derive(varname)[source]
Check if a variable can be derived from components.
- elm_diagnostics.io.derived.derive_variable(run, varname)[source]
Derive a variable from available components.
- Parameters:
- Returns:
Derived variable.
- Return type:
- Raises:
ValueError – If variable cannot be derived or required components are missing.
Sub-gridcell Support
Sub-gridcell hierarchy detection and helpers.
- elm_diagnostics.io.subgrid.detect_subgrid_dims(ds)[source]
Return the set of sub-gridcell dimensions present in a dataset.
If the set is empty, the dataset uses gridcell-averaged output (dov2xy = .true.).
- elm_diagnostics.io.subgrid.has_subgrid(ds)[source]
Check whether a dataset has sub-gridcell dimensions.
- elm_diagnostics.io.subgrid.validate_by_keyword(ds, by)[source]
Validate that the
bykeyword is compatible with the dataset.- Raises:
ValueError – If
byis requested but the dataset is gridcell-averaged, or if the requested level isn’t present.- Return type:
- Parameters:
Units
Unit handling for ELM history variables using pint / pint-xarray.
- elm_diagnostics.io.units.get_registry()[source]
Return the shared pint unit registry.
- Return type:
UnitRegistry
- elm_diagnostics.io.units.normalize_unit_string(raw)[source]
Convert an ELM unit string to a pint-parseable form.
- elm_diagnostics.io.units.parse_units(raw)[source]
Parse an ELM unit string into a pint Unit.
- Return type:
Unit- Parameters:
raw (str)
- elm_diagnostics.io.units.classify_variable(da)[source]
Classify a variable as flux, state, or intensive.
Uses (in priority order): 1. Known variable name lists. 2.
cell_methodsattribute (contains"time: mean"→ flux-like). 3. Unit string inspection (contains/s→ flux).
- elm_diagnostics.io.units.convert_flux_to_cumulative_units(units_str)[source]
Determine the target cumulative units for a flux.
Returns (target_unit_string, seconds_multiplier).
Examples
>>> convert_flux_to_cumulative_units("mm/s") ('mm', 1.0) >>> convert_flux_to_cumulative_units("gC/m^2/s") ('g/m**2', 1.0) >>> convert_flux_to_cumulative_units("W/m^2") ('J/m**2', 1.0)
- elm_diagnostics.io.units.convert_water_to_mm(da)[source]
Convert water storage variable to mm units.
Water mass per unit area (kg/m²) and water depth (mm) are numerically equivalent for liquid water: 1 kg/m² = 1 mm H2O (assuming density = 1000 kg/m³).
This function standardizes the units attribute to “mm” without changing values. Variables already in mm are returned unchanged.
- Parameters:
da (
DataArray) – Water storage variable with units in kg/m², mm, or variants.- Returns:
Variable with units standardized to “mm”. Values are unchanged.
- Return type:
- Raises:
ValueError – If units cannot be converted to mm (e.g., temperature, pressure).
Examples
>>> soilliq = xr.DataArray([100.0, 150.0], attrs={"units": "kg/m2"}) >>> result = convert_water_to_mm(soilliq) >>> result.attrs["units"] 'mm' >>> result.values array([100., 150.])