ADR-0002: Boolean mapper flags (sparse_data / sampled / continuous_*domain) as the transform interface contract#
Field |
Value |
|---|---|
Kind |
ADR |
Status |
Accepted |
Decided |
historical |
Deciders |
Antoine Berchet |
Supersedes |
— |
Superseded by |
— |
Context#
Transforms in CIF’s pipeline exchange data through a shared mapper dict, one entry per
(component, parameter). Data at each end of a transform is held in one of two container
types: a gridded xarray.Dataset (dims time, lev, lat, lon) for full-field data, or a
pandas.DataFrame (one row per point) for sparse data such as observations. A precursor and
its successor transform don’t necessarily agree on which container is expected on each side,
and some transforms (e.g. Lagrangian footprint models) already interpolate to observation
locations internally, while others (Eulerian models) need pyCIF to insert that interpolation
for them. As the number of transforms and plugins grew, this had to be resolved automatically
for the whole pipeline rather than negotiated ad hoc inside each plugin.
See also: the full mapper-field reference in Transforms transform
(docstring of pycif.plugins.transforms), and the auto-wiring implementation in
pycif/plugins/obsoperators/standard/transforms/utils/init_sparse.py.
Decision#
We represent this state as a handful of independent boolean flags on each mapper entry —
sparse_data, sampled, continuous_hdomain, continuous_vdomain (plus domain itself) —
not as a single geometry_kind enumeration. During pipeline construction, init_sparse.py
compares a precursor’s flags against its successor’s and automatically inserts the matching
conversion transform (sparse2sample, array2sampled, regrid, vertical_interpolation,
time_interpolation) whenever they disagree.
Consequences#
Plugin/transform authors declare a handful of independent flags per mapper entry; the pipeline — not the plugin — is responsible for inserting the right conversion between a precursor and its successor.
Because the flags are independent booleans rather than a closed enum, invalid combinations aren’t ruled out by construction — only by the explicit checks each transform’s init step happens to encode (e.g.
init_sparse.pyraisesCifError("Sparse to sample should not happen")ifsampledis set on a target that expects sparse input).Adding a genuinely new data representation means threading a new boolean through every place
sparse_data/sampled/continuous_*domainare checked (init_sparse.py,aggreg_deaggreg_inout.py,fetch_inoutputs.py, and others), rather than adding one enum member — this is real, ongoing maintenance cost, not a hypothetical one.