Variable attributes
Fields Polaris writes to a NetCDF file should say what they are: a
long_name, and units where the quantity has a meaningful unit. Getting
that right takes a little care, because a variable computed from another one
does not start out with a blank slate.
xarray propagates attributes
xarray keeps attrs through binary arithmetic, comparisons, where(),
zeros_like() and NumPy ufuncs such as np.maximum:
>>> bottom_pressure.attrs
{'long_name': 'seafloor pressure', 'units': 'Pa'}
>>> (bottom_pressure / (rho0 * gravity)).attrs
{'long_name': 'seafloor pressure', 'units': 'Pa'}
>>> xr.zeros_like(bottom_pressure).astype(int).attrs
{'long_name': 'seafloor pressure', 'units': 'Pa'}
A depth in meters and a one-based level index both come out of that claiming to be a pressure in Pascals. Older versions of xarray dropped attributes in arithmetic, so code written against that behaviour changed meaning silently on upgrade.
Setting individual keys is not enough to undo it:
# leaves any other attribute the parent had, such as a stale
# ``cell_measures`` or a non-standard ``unit``
ds.bottomDepth.attrs['long_name'] = 'seafloor geometric depth'
ds.bottomDepth.attrs['units'] = 'm'
Nor is xr.set_options(keep_attrs=False): it suppresses propagation through
arithmetic and comparisons, but zeros_like() and where() keep attributes
regardless.
Use set_attrs()
polaris.attrs.set_attrs() replaces a variable’s attributes
wholesale, so a field carries exactly what it is given and nothing it
inherited:
from polaris.attrs import set_attrs
# in place, on a variable already in a dataset
ds['maxLevelCell'] = max_level_cell + 1
set_attrs(
ds.maxLevelCell,
long_name='Index to the last active ocean cell in each column.',
)
# inline, on an expression
ds['bottomDepth'] = set_attrs(
-geom_z_bot, long_name='seafloor geometric depth', units='m'
)
Both patterns work: set_attrs() modifies the array in place and returns it.
ds[name] shares the underlying Variable, so labelling ds.maxLevelCell
reaches the dataset; and ds[name] = da copies the attributes, so two dataset
variables built from one array can still describe themselves differently.
Leave units out for quantities that have no meaningful unit — a one-based
level index, a boolean mask — rather than inventing one. Anything else, such
as a note or a standard_name, can be passed as an extra keyword argument.
CF conventions
A step that writes a netCDF file with xarray or write_netcdf() should pass
the dataset through polaris.cf.add_cf_conventions() first. It
adds CF-1.8 to the Conventions attribute, keeping any other convention
listed, such as the MPAS entry from the MPAS-Tools mesh converter. For an
MPAS mesh, polaris.mesh.attrs.add_mesh_var_attrs() also fills in
units and long_name for the mesh variables MPAS-Tools writes without
them. Both leave what a variable already has alone. The ocean component’s
write methods do both for you (see CF metadata).
A file remapped with ncremap, or cut down to a few of its source’s
variables, keeps attributes such as bounds, cell_measures and
grid_mapping that name variables it no longer has, and the CF checker
reports each as an error. polaris.cf.drop_dangling_references()
drops them. polaris.cf.add_lat_lon_attrs() labels the lat
and lon coordinates of a regular latitude-longitude grid.
To have the file checked after the step runs, opt it in with
polaris.Step.add_cf_check() (see CF compliance checks).
A units string must be one udunits parses: the plain CF form (m s-1, N m-2, radians), with 1 for a dimensionless quantity, never unitless,
dimensionless or PSU.