Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4c01f3f
Merge pull request #56 from NCAR/main
jukent May 8, 2024
45256b4
Merge pull request #57 from NCAR/main
jukent Jun 28, 2024
c70cf22
Merge branch 'main' of https://github.com/jukent/geocat-comp
jukent Oct 9, 2025
92d121c
zonal_mpsi fx
jukent Oct 9, 2025
383443d
init and release-notes
jukent Oct 9, 2025
ac52e9f
index.rst
jukent Oct 9, 2025
b68952a
Merge branch 'main' of https://github.com/NCAR/geocat-comp into zonal…
jukent Oct 9, 2025
66fa9a7
update env
jukent Oct 9, 2025
f28b382
hybrid_pressure kwarg and input validation
jukent Oct 16, 2025
8003b7a
add tests
jukent Oct 23, 2025
3db32b7
precommit rm
jukent Oct 23, 2025
8747e81
pre-commit release-notes
jukent Oct 23, 2025
84e191d
add uxarray to min-deps
jukent Oct 23, 2025
bb53840
add uxarrary to docs
jukent Oct 23, 2025
f73f9ad
test
jukent Oct 23, 2025
8226fa3
add test files
jukent Oct 24, 2025
ac66118
use custom datafiles
jukent Dec 3, 2025
19c6827
update test
jukent Dec 3, 2025
5862a3f
Merge branch 'main' of https://github.com/NCAR/geocat-comp into zonal…
jukent Feb 3, 2026
f6ce027
cleaned up function, using new testing data, fleshed out tests
jukent Feb 12, 2026
0d44645
pre-commit and release notes
jukent Feb 12, 2026
54906c9
find coords
jukent Feb 12, 2026
18dd80c
rm .values call
jukent Feb 12, 2026
728337f
some style
jukent Feb 12, 2026
51b15ee
text
jukent Feb 12, 2026
7408af9
rename
jukent Feb 12, 2026
d582684
CF error handling
jukent Feb 12, 2026
ccd117b
List what was tried in units warning
jukent Feb 12, 2026
886412b
update an error message
jukent Feb 12, 2026
1bd1b0d
change fx name, let users add input variables, add wrapper
jukent Feb 12, 2026
275b3ad
VSCode thought it was being helpful
jukent Feb 13, 2026
0e13cf2
docstring update
jukent Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build_envs/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ dependencies:
- nbsphinx
- xskillscore>=0.0.17
- nc-time-axis
- uxarray
1 change: 1 addition & 0 deletions build_envs/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
- pandas
- xarray
- xskillscore>=0.0.17
- uxarray>=2025.12.0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say yes. There were updates to UXarray's where and some wrappers that were previously roadblocks to this function. Perhaps conda will naturally resolve the environment to the latest version, but I'd like to let more time pass before trusting that.

# Packages listed below are for testing
- geocat-datafiles
- netcdf4
Expand Down
1 change: 1 addition & 0 deletions build_envs/min-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ dependencies:
- pytest
- pytest-cov
- geocat-datafiles
- uxarray
11 changes: 11 additions & 0 deletions docs/release-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
Release Notes
=============


vYYYY.MM.## (unreleased)
------------------------
This release has updates to our internal structure to ingest UXarray datasets in a new zonal meridional stream function for unstructured grid datafiles using UXarray in ``meteorology.zonal_meridional_psi``. There are also two new helper functioins ``util._find_coord`` and ``util._find_optional_coord``

New Features
^^^^^^^^^^^^
* Zonal Meridional Stream Function for unstructured grid datafiles using
UXarray in ``meteorology.zonal_meridional_psi`` by `Julia Kent`_ in (:pr:`773`)


v2026.01.0 (January 27, 2026)
-----------------------------
This release updates ``calendar_average`` and ``climate_anomaly`` to allow for
Expand Down
1 change: 1 addition & 0 deletions docs/user_api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Meteorology
relhum_water
saturation_vapor_pressure
saturation_vapor_pressure_slope
zonal_meridional_psi

Spherical Harmonics
^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions geocat/comp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
saturation_vapor_pressure_slope,
delta_pressure,
dpres_plev,
zonal_meridional_psi,
zonal_mpsi,
)
from .spherical import decomposition, recomposition, scale_voronoi
from .stats import eofunc, eofunc_eofs, eofunc_pcs, eofunc_ts, pearson_r, nmse
Expand Down
126 changes: 126 additions & 0 deletions geocat/comp/gc_util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing
import warnings


def _generate_wrapper_docstring(
Expand Down Expand Up @@ -27,3 +28,128 @@ def _generate_wrapper_docstring(

# assign docstring to wrapper function
setattr(wrapper_fcn, '__doc__', wrapper_docstring)


def _find_var(
ds,
standard_name=None,
long_name=None,
possible_names=None,
units=None,
description='variable',
):
"""
Find a variable using CF-compliant checks.

Searches in priority order:
1. CF standard_name attribute match (if standard_names provided)
2. Name attribute match (if long_names provided)
3. Direct variable name match (if possible_names provided)
4. Units match (if units provided)

Parameters
----------
ds : xr.Dataset or ux.UxDataset
The dataset to search
standard_name : str, optional
CF standard_name to check in attrs
long_name : str, optional
long_name to check in attrs
possible_names : list of str, optional
List of possible variable names to check first
units : str, optional
Possible units to check in attrs
description : str, optional
String

Returns
-------
str
The name of the found variable

Raises
------
KeyError
If no matching variable is found
"""
error_parts = []

# First try CF standard_name attribute match
if standard_name:
for var_name in ds.data_vars:
var_attrs = ds[var_name].attrs
if var_name == standard_name or standard_name in var_attrs.get(
'standard_name', ''
):
Comment on lines +81 to +83
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for this and all the following) I feel like we should make this at least a little more robust, like

  • checking against the upper and lower case versions of the string
  • maybe allowing for some sort of partial match, like "wind" would also match "abc_wind123" (I know that's not a super realistic example) with regex matching

return var_name
error_parts.append(f"Tried standard_name: {standard_name}. ")

# Then try long_name attribute match
if long_name:
for var_name in ds.data_vars:
var_attrs = ds[var_name].attrs
if long_name in var_attrs.get('long_name', ''):
return var_name
error_parts.append(f"Tried long_name: {long_name}. ")

# Then try direct name match
if possible_names:
for name in possible_names:
if name in ds:
return name
error_parts.append(f"Tried names: {possible_names}. ")

# Finally try units match (less reliable)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using units here still concerns me. I'm not convinced that units are a good way of determining a match.

if units:
for var_name in ds.data_vars:
var_attrs = ds[var_name].attrs
if 'units' in var_attrs:
if units in var_attrs.get('units', ''):
warnings.warn(
f"Found {description} '{var_name}' using units attribute only. "
f"This is unreliable - multiple variables may share the same units. "
f"Please verify this is correct and add CF standard_name. {error_parts}",
UserWarning,
stacklevel=3,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why stacklevel=3 here? If my understanding is correct, this would make sense for when _find_var is called by _find_optional_var, but not when _find_var is called directly

)
return var_name
error_parts.append(f"Tried units: {units}. ")

raise KeyError(f"Could not find {description} in dataset. {' '.join(error_parts)}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise KeyError(f"Could not find {description} in dataset. {' '.join(error_parts)}")
raise KeyError(f"Could not find {description} in dataset. {''.join(error_parts)}")



def _find_optional_var(
ds,
standard_name=None,
long_name=None,
possible_names=None,
units=None,
description=None,
):
"""
Find an optional variable using CF-compliant checks.

Parameters
----------
ds : xr.Dataset or ux.UxDataset
The dataset to search
standard_name : str, optional
CF standard_name to check in attrs
long_name : str, optional
Long_name to check in attrs
possible_names : list of str, optional
List of possible variable names
units : str, optional
Possible units to check in attrs

Returns
-------
str or None
The name of the found coordinate, or None if not found
"""
try:
return _find_var(
ds, standard_name, long_name, possible_names, units, description
)
except KeyError:
return None
8 changes: 7 additions & 1 deletion geocat/comp/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import metpy.interpolate
import numpy as np
import xarray as xr
import uxarray as ux

supported_types = typing.Union[xr.DataArray, np.ndarray]

Expand Down Expand Up @@ -383,7 +384,12 @@ def pressure_at_hybrid_levels(psfc, hya, hyb, p0=100000.0):
"""
# check input types
in_types = [type(psfc), type(hya), type(hyb)]
if not set(in_types).issubset({xr.DataArray, np.ndarray}) or len(set(in_types)) > 1:
if (
not set(in_types).issubset(
{xr.DataArray, np.ndarray, ux.core.dataarray.UxDataArray}
)
or len(set(in_types)) > 1
):
raise TypeError(
"psfc, hya, and hyb must be xarray DataArrays or all numpy arrays"
)
Expand Down
Loading
Loading