-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consistent variable naming #30
Changes from 9 commits
9424173
ad336fe
803be5e
91036a9
4060ef6
e189962
b49fc40
1c8d9c1
a434d2a
60bc654
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
from .nomenclature import get_field | ||
from ._version import get_versions | ||
|
||
__version__ = get_versions()["version"] | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,168 @@ | ||||||
""" | ||||||
To ensure that all functions in eurec4a-environment can access the variables | ||||||
required the module uses a common nomenclature for variable names throughout. | ||||||
To add a new variable to the nomenclature simply a) add a new constant below | ||||||
setting the assumed field name and b) add (if available) the CF-conventions | ||||||
standard name mapping in `CF_STANDARD_NAMES` if the variable has a "standard | ||||||
name" (See http://cfconventions.org/standard-names.html for the list of | ||||||
"standard names") | ||||||
""" | ||||||
import inspect | ||||||
import xarray as xr | ||||||
try: | ||||||
import cfunits | ||||||
HAS_UDUNITS2 = True | ||||||
except FileNotFoundError: | ||||||
import warnings | ||||||
HAS_UDUNITS2 = False | ||||||
|
||||||
|
||||||
# TODO: update temperature to be called `ta` once JOANNE dataset is released | ||||||
# which uses this definition | ||||||
TEMPERATURE = "T" | ||||||
# TODO: update altitude to be called `alt` once JOANNE dataset is released | ||||||
# which uses this definition | ||||||
ALTITUDE = "height" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I'll update these. Should I update the JOANNE dataset in the intake catalog already? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did these get updated? |
||||||
RELATIVE_HUMIDITY = "rh" | ||||||
POTENTIAL_TEMPERATURE = "theta" | ||||||
PRESSURE = "p" | ||||||
|
||||||
# From CF-conventions: "specific" means per unit mass. Specific humidity is | ||||||
# the mass fraction of water vapor in (moist) air. | ||||||
SPECIFIC_HUMIDITY = "q" | ||||||
|
||||||
# From CF-conventions: Speed is the magnitude of velocity. Wind is defined as | ||||||
# a two-dimensional (horizontal) air velocity vector, with no vertical | ||||||
# component. (Vertical motion in the atmosphere has the standard name | ||||||
# upward_air_velocity.) The wind speed is the magnitude of the wind velocity | ||||||
WIND_SPEED = "wspd" | ||||||
|
||||||
# From CF-conventions: "Eastward" indicates a vector component which is | ||||||
# positive when directed eastward (negative westward). Wind is defined as a | ||||||
# two-dimensional (horizontal) air velocity vector, with no vertical component. | ||||||
# (Vertical motion in the atmosphere has the standard name | ||||||
# upward_air_velocity.) | ||||||
ZONAL_WIND = "u" | ||||||
|
||||||
# From CF-conventions: "Northward" indicates a vector component which is | ||||||
# positive when directed northward (negative southward). Wind is defined as a | ||||||
# two-dimensional (horizontal) air velocity vector, with no vertical component. | ||||||
# (Vertical motion in the atmosphere has the standard name | ||||||
# upward_air_velocity.) | ||||||
MERIDIONAL_WIND = "v" | ||||||
|
||||||
|
||||||
CF_STANDARD_NAMES = { | ||||||
TEMPERATURE: "air_temperature", | ||||||
ALTITUDE: "geopotential_height", | ||||||
RELATIVE_HUMIDITY: "relative_humidity", | ||||||
POTENTIAL_TEMPERATURE: "air_potential_temperature", | ||||||
PRESSURE: "air_pressure", | ||||||
SPECIFIC_HUMIDITY: "specific_humidity", | ||||||
WIND_SPEED: "wind_speed", | ||||||
ZONAL_WIND: "eastward_wind", | ||||||
MERIDIONAL_WIND: "northward_wind", | ||||||
} | ||||||
|
||||||
|
||||||
def _get_calling_function_name(): | ||||||
curframe = inspect.currentframe() | ||||||
calframe = inspect.getouterframes(curframe, 2) | ||||||
return calframe[1][3] | ||||||
|
||||||
|
||||||
class FieldMissingException(Exception): | ||||||
pass | ||||||
|
||||||
|
||||||
def get_field(ds, field_name, units=None): | ||||||
""" | ||||||
Get field described by `field_name` in dataset `ds` and ensure it is in the | ||||||
units defined by `units` (if `units` != None). The units definition is any | ||||||
unit definition supported by | ||||||
[UDUNITS](https://www.unidata.ucar.edu/software/udunits/) | ||||||
""" | ||||||
if field_name in ds: | ||||||
da = ds[field_name] | ||||||
else: | ||||||
|
||||||
calling_function_name = _get_calling_function_name() | ||||||
|
||||||
if field_name not in CF_STANDARD_NAMES: | ||||||
raise FieldMissingException( | ||||||
f"Couldn't find the variable `{field_name}` in the provided dataset." | ||||||
f" To use {calling_function_name} you need to provide a variable" | ||||||
f" with the name `{field_name}`" | ||||||
) | ||||||
else: | ||||||
standard_name = CF_STANDARD_NAMES[field_name] | ||||||
matching_dataarrays = {} | ||||||
vars_and_coords = list(ds.data_vars) + list(ds.coords) | ||||||
for v in vars_and_coords: | ||||||
print(v) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want the print statement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, thanks :) |
||||||
if ds[v].attrs.get("standard_name", None) == standard_name: | ||||||
matching_dataarrays[v] = ds[v] | ||||||
|
||||||
if len(matching_dataarrays) == 0: | ||||||
raise FieldMissingException( | ||||||
f"Couldn't find the variable `{field_name}` in the provided dataset." | ||||||
f" To use {calling_function_name} you need to provide a variable" | ||||||
f" with the name `{field_name}` or set the `standard_name`" | ||||||
f" attribute to `{standard_name}` for one or more of the variables" | ||||||
" in the dataset" | ||||||
) | ||||||
elif len(matching_dataarrays) == 1: | ||||||
da = list(matching_dataarrays.values())[0] | ||||||
else: | ||||||
dims = [da.dims for da in matching_dataarrays.values()] | ||||||
if len(set(dims)) == 1: | ||||||
# all variables have the same dims, so we can create a | ||||||
# composite data-array out of these | ||||||
var_names = list(matching_dataarrays.keys()) | ||||||
var_dataarrays = list(matching_dataarrays.values()) | ||||||
|
||||||
# TODO: should we check units here too? | ||||||
da_combined = xr.concat(var_dataarrays, dim="var_name") | ||||||
da_combined.coords["var_name"] = var_names | ||||||
da = da_combined | ||||||
else: | ||||||
raise FieldMissingException( | ||||||
"More than one variable was found in the dataset with" | ||||||
f" the standard name `{standard_name}`, but these couldn't" | ||||||
" be merged to a single xarray.DataArray because they" | ||||||
" don't exist in the same coordinate system" | ||||||
) | ||||||
pass | ||||||
|
||||||
if units is None: | ||||||
return da | ||||||
else: | ||||||
# ensure that output is in correct units | ||||||
if "units" not in da.attrs: | ||||||
field_name = da.name | ||||||
raise Exception( | ||||||
f"Units haven't been set on `{field_name}` field in dataset" | ||||||
) | ||||||
if da.attrs["units"] == units: | ||||||
return da | ||||||
|
||||||
if not HAS_UDUNITS2: | ||||||
raise Exception( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be better to have a more specific exception here |
||||||
"To do correct unit conversion udunits2 is required, without" | ||||||
" it no unit conversion will be done. udunits2 can be installed" | ||||||
" with conda, `conda install -c conda-forge udunits2` or see" | ||||||
" https://stackoverflow.com/a/42387825 for general instructions" | ||||||
) | ||||||
|
||||||
old_units = cfunits.Units(da.attrs["units"]) | ||||||
new_units = cfunits.Units(units) | ||||||
if old_units == new_units: | ||||||
return da | ||||||
else: | ||||||
values_converted = cfunits.Units.conform(da.values, old_units, new_units) | ||||||
attrs = dict(da.attrs) | ||||||
attrs["units"] = units | ||||||
da_converted = xr.DataArray( | ||||||
values_converted, coords=da.coords, dims=da.dims, attrs=attrs | ||||||
) | ||||||
return da_converted |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,35 @@ | ||
import xarray as xr | ||
|
||
from ... import nomenclature as nom | ||
|
||
|
||
def find_inversion_height_grad_RH( | ||
ds, altitude="alt", rh="rh", smoothing_win_size=None, z_min=1500, z_max=4000.0 | ||
ds, | ||
altitude=nom.ALTITUDE, | ||
rh=nom.RELATIVE_HUMIDITY, | ||
smoothing_win_size=None, | ||
z_min=1500, | ||
z_max=4000.0, | ||
): | ||
|
||
""" | ||
Returns inversion height defined as the maximum in the vertical gradient of RH | ||
""" | ||
""" | ||
|
||
ds_lowertroposhere = ds.sel({altitude: slice(z_min, z_max)}) | ||
da_rh = nom.get_field(ds=ds_lowertroposhere, field_name=rh) | ||
da_z = nom.get_field(ds=ds_lowertroposhere, field_name=altitude) | ||
|
||
if smoothing_win_size: | ||
RH = ( | ||
ds_lowertroposhere[rh] | ||
.rolling(alt=smoothing_win_size, min_periods=smoothing_win_size, center=True) | ||
.mean(skipna=True) | ||
) | ||
RH = da_rh.rolling( | ||
alt=smoothing_win_size, min_periods=smoothing_win_size, center=True | ||
).mean(skipna=True) | ||
else: | ||
RH = ds_lowertroposhere[rh] | ||
RH = da_rh | ||
|
||
RHg = RH.differentiate(coord=altitude) | ||
ix = RHg.argmin(dim=altitude, skipna=True) | ||
da_z = RHg.isel({ altitude: ix }).alt | ||
da_z = RHg.isel({altitude: ix})[altitude] | ||
da_z.attrs["long_name"] = "inversion layer height (from RH gradient)" | ||
da_z.attrs["units"] = "m" | ||
da_z.attrs["units"] = da_z.units | ||
|
||
return da_z |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just to be consistent with the latest variable names in JOANNE - most variable names are finalised, but some are still being discussed.. :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem :)