API reference¶
The entry points HealDA supports for use from other code. Everything else under
healda may change without notice.
Inference¶
healda.inference
¶
Analyses from a trained HealDA checkpoint and observation tables.
load_da_model rebuilds the network the way the run trained it, from the
loop.json the checkpoint carries; DAModel.run_analysis runs the recipe's own
observation loaders and transform on in-memory cycle tables, and
DAModel.run_analysis_unified the transform alone on a unified table; both return
physical fields. Downstream wrappers (Earth2Studio) build on this and add nothing to the
numerics.
DAModel
dataclass
¶
ir_channels
property
¶
Channel numbers the recipe reads per IR sounder; it reads no others.
frame_times
¶
The time_length frames ending at analysis_time.
observation_window
¶
Span of observation times one analysis reads, [start, end).
run_analysis
¶
run_analysis(
analysis_times: AnalysisTimes,
*,
satellite_tables: Mapping[str, CycleTableSource]
| None = None,
gpsro_tables: CycleTableSource | None = None,
satwnd_tables: CycleTableSource | None = None,
prepbufr_tables: CycleTableSource | None = None,
) -> Tensor
Analyses from per-family observation tables, run through the recipe's loaders.
Each analysis is the last frame of a time_length window fed with the
observations of every frame. The loaders quality control, thin and normalize the
tables as in training. Analyses run one at a time.
Parameters:
-
analysis_times(AnalysisTimes) –Analysis times, UTC, anything
pd.DatetimeIndexaccepts. -
satellite_tables(Mapping[str, CycleTableSource] | None, default:None) –Per sensor name,
{cycle: table}inschema.table_schema(sensor). A sensor not passed contributes no observations. -
gpsro_tables(CycleTableSource | None, default:None) –{cycle: table}inschema.table_schema("gpsro"). -
satwnd_tables(CycleTableSource | None, default:None) –{cycle: table}inschema.table_schema("satwnd"). -
prepbufr_tables(CycleTableSource | None, default:None) –{cycle: table}inschema.table_schema("prepbufr"). A family not passed contributes no observations.
Returns:
-
Tensor–(len(analysis_times), len(self.channels), 721, 1440)float32 analyses in -
Tensor–physical units on the 0.25 degree lat/lon grid, on
self.device. Bounded -
Tensor–channels (humidity, water columns, snow depth, soil moisture, sea ice and
-
Tensor–cloud fractions) are clipped to
state_transforms.PHYSICAL_BOUNDS.
run_analysis_unified
¶
Analyses from a unified observation table, skipping the loaders.
Each frame reads the rows whose DA_window is one of its windows and passes
them to featurization unchanged. Analyses run one at a time.
Parameters:
-
analysis_times(AnalysisTimes) –Analysis times, UTC, anything
pd.DatetimeIndexaccepts. -
observations(Table) –One table in
schema.table_schema("unified"), rows already thinned, quality controlled and normalized; rows outside the analyses' windows are not read.
Returns:
-
Tensor–(len(analysis_times), len(self.channels), 721, 1440)float32 analyses in -
Tensor–physical units on the 0.25 degree lat/lon grid, on
self.device. Bounded -
Tensor–channels (humidity, water columns, snow depth, soil moisture, sea ice and
-
Tensor–cloud fractions) are clipped to
state_transforms.PHYSICAL_BOUNDS.
Raises:
-
ValueError–observationsdoes not match the unified schema, or a read row holds asensor_idthe transform does not group or an id outside the network's embedding tables.
to_physical
¶
Model space (b, C, lat, lon) -> physical units, bounded channels clipped
to their physical range, fills re-imposed.
load_da_model
¶
load_da_model(
checkpoint: str | PathLike,
device: device | str,
*,
extra_filters: ObsFilters | None = None,
) -> DAModel
Rebuild the trained network and its observation pipeline from a checkpoint.
The recipe is the loop.json stored in the checkpoint; it must be an NNJA lat/lon
recipe. The first call downloads the ERA5 static fields into the healda
cache.
Parameters:
-
checkpoint(str | PathLike) –A
.checkpointfile written byhealda-train. -
device(device | str) –Device the network runs on.
-
extra_filters(ObsFilters | None, default:None) –Observations removed beyond the recipe's own filters. Defaults to
inference_filters(): the dated channel denials, and the provider quality flags of tables that carry them.
Returns:
-
DAModel–The model, ready for
run_analysisandrun_analysis_unified.
Raises:
-
ValueError–The checkpoint has no
loop.jsonor is not an NNJA lat/lon recipe.
Earth2Studio observation adapter¶
healda.observations.adapters.e2s_nnja
¶
Earth2Studio NNJA frames as the canonical NNJA archive tables.
earth2studio.data.NNJAObsConv (PrepBUFR u, v, q, t, pres;
GPS-RO gps, gps_refractivity), NNJAObsSatwnd (u, v) and
NNJAObsSat return raw decoded observations in one of two layouts
(layout):
long: one row per observation, its variable invariableand its value inobservation;wide: one row per report level, profile level, wind or footprint, each variable's value inobservation_<variable>and, for conventional data, its quality mark inquality_<variable>. A satellite frame holds one sensor, each footprint's channels as fixed-size listsqualityandobservation_<variable>over the sensor's channel axis, which the frame carries inattrs["channels"].
analysis_tables takes wide frames and analysis_tables_long long ones. Both
restate them in the column contract of healda's NNJA PrepBUFR, GPS-RO, SATWND and wide
satellite archive tables, deriving what the ETL derives with the same code, so the
loaders screen, thin and emit them exactly as they do archive rows, and both give the
same tables. They return DAModel.run_analysis's table arguments: each a
{cycle: table} CycleTables, satellite_tables one per sensor.
The module holds the wide layout and the archive builders both layouts share; the long layout's regrouping is its last section.
Satellite values stay in kelvin as earth2studio returns them. The IR sounders' columns
are brightness_temperature__ch_NNNNN with field metadata nnja.value_unit = K,
which NNJAWideLoader reads without a Planck inversion.
Rows are keyed by cycle_time, the NCEP file each was decoded from, which the
Earth2Studio NNJA sources fill: an observation at the shared edge of two files' windows
is in both files, and stays in both, as in the archive.
Usage, every stream at once from wide frames::
tables = analysis_tables(
prepbufr=prepbufr, gpsro=gpsro, satwnd=satwnd, sat={"atms": atms},
ir_channels=...,
)
model.run_analysis(times, **tables)
analysis_tables
¶
analysis_tables(
*,
prepbufr: DataFrame | None = None,
gpsro: DataFrame | None = None,
satwnd: DataFrame | None = None,
sat: Mapping[str, DataFrame] | None = None,
ir_channels: str | Mapping[str, Sequence[int]] | None,
workers: int | None = None,
) -> dict
DAModel.run_analysis table arguments from wide Earth2Studio NNJA frames.
Each stream is its own frame: prepbufr and gpsro from NNJAObsConv,
satwnd from NNJAObsSatwnd, and sat a mapping from sensor to that
sensor's NNJAObsSat frame. A stream or sensor not passed gives no tables, which
the loaders read as no observations. Long frames go to analysis_tables_long.
Every footprint, report, occultation and wind comes from one file, so each stream
is cut by cycle_time and the pieces adapted in parallel on workers threads (default
the smaller of 32 and the CPUs this process may run on); the result equals adapting each
whole stream.
The function returns once the streams are cut. Each {cycle: table} is a
CycleTables whose lookups wait for that cycle's adaptation.
analysis_tables_long
¶
analysis_tables_long(
*,
conv: DataFrame | None = None,
satwnd: DataFrame | None = None,
sat: DataFrame | None = None,
sensors: Sequence[str] = (),
ir_channels: str | Mapping[str, Sequence[int]] | None,
workers: int | None = None,
) -> dict
DAModel.run_analysis table arguments from long frames: conv an
NNJAObsConv(original_event=True) frame of any PrepBUFR and GPS-RO variables,
satwnd an NNJAObsSatwnd frame and sat an NNJAObsSat frame of any of
sensors.
Observation table schemas¶
healda.observations.schema
¶
Arrow schemas of the observation tables healda reads and emits.
The documented API is table_schema and validate. They are the contracts of two ways in:
- the NNJA archive tables, per family (
table_schema("prepbufr"), ...) and per satellite sensor (table_schema(sensor, ir_channels)), which the NNJA loaders andadapters.e2s_nnja.analysis_tablesread; - the unified table,
table_schema("unified"), one row per observation, whichDAModel.run_analysis_unifiedtakes.
footprint_fields and channel_field are the satellite tables' fields, which the NNJA ETL
writes the archive with.
Each row of the unified table is one scalar measurement: a radiosonde level with u, v, t and q is four rows, and a satellite footprint is one row per channel.
table_schema
¶
The schema of kind: "prepbufr", "gpsro", "satwnd", a satellite
sensor, or "unified".
For a satellite sensor, ir_channels is a preset name ("ir32", "ir48") or a
mapping of sounder to channel numbers; the channels it names are required, and
without it every channel of the sensor's vocabulary is optional and at least one is
required. IASI and CrIS columns are brightness temperature in K.
"unified" is the table DAModel.run_analysis_unified takes and the NNJA
loaders emit: one row per observation, thinned, quality controlled and normalized.
validate
¶
validate(
table: Table,
kind: str,
ir_channels: str
| Mapping[str, Sequence[int]]
| None = None,
) -> None
Raise ValueError naming every column of table that kind's reader cannot read.
kind is as in table_schema. For a satellite sensor, the value columns are checked
as brightness temperature when table has any, as the loader reads them.
Training presets¶
One YAML file per preset in src/healda/configs/loops/, each extending another, read by
healda-train --config; --verbose prints the resolved loop in the same form.
healda.training.presets
¶
Training presets as YAML files, one per preset, each a few changes over another.
A preset file has up to three top-level keys. extends names the preset this one starts
from; loop holds the fields that differ from it, nested the way the training loop
dataclass is (a mapping under obs_config changes only the keys it lists, a list or
scalar replaces the parent's value). Without extends, loop is the whole loop and
anything missing takes the dataclass default. aliases lists other names that resolve
to the same preset. The file's stem is the preset name.
The shipped presets live in healda.configs.loops and may only extend each other. A file
passed to healda-train --config may extend any shipped preset, so a new experiment is a
file with extends and the handful of fields it changes. healda-train --verbose
prints the resolved loop in this form, every field written out.
The loop type is passed in rather than imported, so the training CLI can import this module to fill its registry without a cycle.
load_preset
¶
load_preset(
path: str | PathLike,
loop_type: Type[T],
presets: Mapping[str, T] | None = None,
) -> tuple[T, list[str]]
Return the loop in path and its alias names.
presets is where extends is looked up, normally the training CLI's registry.
load_preset_dir
¶
Every *.yaml in directory under its stem and each of its aliases.
extends resolves to another file in the directory, by stem or alias, in any order.
to_yaml
¶
loop as a preset file with every field written out.
Command-line entry points¶
healda-train and healda-inference-local are the installed console scripts.
Run either with --help for the authoritative option list.
healda.cli.train
¶
Train HealPix Data Assimilation model
healda.cli.inference_local
¶
Run a trained checkpoint over a date range of local data and write the analysis.
Reads the observations and targets the checkpoint's recipe names from local storage,
loads a checkpoint written by healda-train, and writes the analysis zarr. The
checkpoint's loop.json supplies the geometry. Launch under torchrun -m healda.cli.inference_local.