Skip to content

Commit 568c16d

Browse files
committed
Merge branch 'main' into split_at
2 parents a2300c8 + dd7617d commit 568c16d

24 files changed

Lines changed: 12692 additions & 9559 deletions

.github/workflows/conda_test.yml

Lines changed: 0 additions & 57 deletions
This file was deleted.

.github/workflows/lint.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Linting
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
lint:
7+
name: linting
8+
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- uses: actions/checkout@v7
13+
14+
- name: Set up Pixi Environment
15+
uses: prefix-dev/setup-pixi@v0.10.0
16+
with:
17+
cache: true
18+
manifest-path: py_gnome/pixi.toml
19+
environments: dev312
20+
21+
- name: Lint
22+
working-directory: py_gnome
23+
# continue-on-error: true
24+
run: pixi run lint

.github/workflows/pixi_test.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ on:
99
jobs:
1010
test:
1111
strategy:
12+
fail-fast: false
1213
# Test against multiple environments (e.g., different Python versions)
1314
matrix:
1415
environment: [test312, test313, test314]
15-
os: [ubuntu-latest, windows-latest, macos-latest]
16+
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
1617
runs-on: ${{ matrix.os }}
1718

1819
steps:
1920
- name: Checkout code
2021
uses: actions/checkout@v4
2122

2223
- name: Setup Pixi
23-
uses: prefix-dev/setup-pixi@v0.8.3
24+
uses: prefix-dev/setup-pixi@v0.10.0
2425
with:
2526
# Specify which environment from your pixi.toml to install
2627
environments: ${{ matrix.environment }}

.gitignore

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
# generated test files
2-
*noncompliant_sgrid.nc
3-
*sgrid_deltares.nc
4-
*sgrid_deltares_no_opt_attr.nc
5-
*sgrid_roms.nc
6-
*sgrid_wrf.nc
7-
*sgrid_wrf_2.nc
2+
# NOTE: if we need these, they should be in a
3+
# defined place, and ignored in a tests/.gitignore file
4+
# *noncompliant_sgrid.nc
5+
# *sgrid_deltares.nc
6+
# *sgrid_deltares_no_opt_attr.nc
7+
# *sgrid_roms.nc
8+
# *sgrid_wrf.nc
9+
# *sgrid_wrf_2.nc
10+
11+
# version file (auto-generated)
12+
gridded/_version.py
13+
14+
# version file (auto-generated)
15+
gridded/_version.py
816

917
# OS-X stuff
1018
.DS_Store

docs/conda_requirements_docs.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
sphinx>=8.2.3
2+
sphinx-rtd-theme

docs/source/api_reference.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,17 @@ The user can now plot the time series for a given location and times, without ha
6565
Duck Typing
6666
-----------
6767

68-
The Grid objects (Grid, Time, Depth) are for the most part "duck typed", rather than strict subclassing. Though there are base classes that provide shared functionality.
68+
The Grid objects (Grid, Time, Depth) are for the most part "duck typed", rather than strict subclassing, though there are base classes that provide shared functionality.
6969

70-
We are trying to be clear about the "public" vs "private" API by using leading underscores for methods and attributes not intended for external use.
70+
We are trying to be clear about the "public" vs "private" API by using leading underscores for methods and attributes not intended for external use, but it's not as consitent as it should be.
7171

7272

7373
Lazy loading / data arrays
7474
--------------------------
7575

7676
Many of the datasets users need to work with can be quite large. As a result it is impractical to load entire datasets into memory at once. ``gridded`` for the most part shifts the burden of handling lazy loading to external libraries, and does this by keeping data stored in a "numpy array-like" objects. Users can use pure numpy arrays, or any object that "acts" like a numpy array. This should allow ``gridded`` to work with netcdf variables, hdf5 arrays, dask arrays, etc.
7777

78-
In practice, there is no clear definition of "array-like", so ``gridded`` has defined its own definition, based on features we know we need. But it is assumed that nd indexing behaves that same as numpy arrays -- as there is no way to easily confirm that. This is a goal, but in fact, only numpy arrays and ``netCDF4 Variables`` have been implimented and tested. In the future, we may use ``xarray`` as a single abstration layer, rather than rolling our own.
78+
In practice, there is no clear definition of "array-like", so ``gridded`` has defined its own definition, based on features we know we need. But it is assumed that nd indexing behaves that same as numpy arrays -- as there is no way to easily confirm that. This is a goal, but in fact, only numpy arrays and ``netCDF4 Variables`` have been implemented and tested. In the future, we may use ``xarray`` as a single abstraction layer, rather than rolling our own.
7979

8080
To support that, we support converting and testing for array-like with:
8181

@@ -85,7 +85,7 @@ and
8585

8686
``gridded.utils.isarraylike()``
8787

88-
Those utilities will be updated as new needs arrise.
88+
Those utilities will be updated as new needs arise.
8989

9090
Reference
9191
=========

gridded/depth.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
22
import warnings
33

44
import numpy as np
@@ -8,11 +8,7 @@
88
from gridded.utilities import (
99
can_create_class,
1010
get_dataset,
11-
merge_var_search_dicts,
1211
parse_filename_dataset_args,
13-
search_dataset_for_any_long_name,
14-
search_dataset_for_variables_by_longname,
15-
search_dataset_for_variables_by_varname,
1612
search_netcdf_vars,
1713
interpolate_rho_to_psi,
1814
)
@@ -259,7 +255,8 @@ def interpolation_alphas(
259255
indices = np.ma.MaskedArray(data=idxs, mask=np.zeros((len(idxs)), dtype=bool))
260256

261257
alphas = np.ma.MaskedArray(
262-
data=np.empty((len(points)), dtype=np.float64) * np.nan, mask=np.zeros((len(points)), dtype=bool)
258+
data=np.full((len(points),), np.nan, dtype=np.float64),
259+
mask=np.zeros((len(points)), dtype=bool)
263260
)
264261

265262
# set above surface and below seafloor alphas to allow future filtering
@@ -296,7 +293,7 @@ def interpolation_alphas(
296293

297294
alphas[within_layer] = (depths[within_layer] - L0) / (L1 - L0)
298295

299-
if any(np.isnan(alphas)):
296+
if np.isnan(alphas).any():
300297
raise ValueError("Some alphas are still unmasked and NaN. Please file a bug report")
301298

302299
return indices, alphas
@@ -423,16 +420,18 @@ def from_netCDF(
423420
424421
:param terms: Direct mapping of component name to netCDF variable name. Use
425422
this if auto detection fails. Partial definition is allowable.
426-
Unspecified terms will use the value in `.default_names`.
427-
::
428-
{'Cs_r': 'Cs_r',
429-
'Cs_w': 'Cs_w',
430-
's_rho': 's_rho'),
431-
's_w': 's_w',
432-
'bathymetry': 'h',
433-
'hc': 'hc'),
434-
'zeta': 'zeta')
435-
}
423+
Unspecified terms will use the value in `.default_names`. ::
424+
425+
{
426+
'Cs_r': 'Cs_r',
427+
'Cs_w': 'Cs_w',
428+
's_rho': 's_rho'),
429+
's_w': 's_w',
430+
'bathymetry': 'h',
431+
'hc': 'hc'),
432+
'zeta': 'zeta')
433+
}
434+
436435
:type terms: dict
437436
438437
:param name: Human-readable name for this object
@@ -490,7 +489,9 @@ def from_netCDF(
490489
time = Time.constant_time()
491490
else:
492491
time = Time.from_netCDF(
493-
dataset=zeta_var._grp, # zeta_var should be a netCDF4.Variable, so its _grp attribute should be the dataset it belongs to
492+
# zeta_var should be a netCDF4.Variable,
493+
# so its _grp attribute should be the dataset it belongs to
494+
dataset=zeta_var._grp,
494495
datavar=zeta_var,
495496
origin=time_origin,
496497
displacement=displacement,
@@ -678,7 +679,8 @@ def interpolation_alphas(
678679
data=-np.ones((len(points)), dtype=np.int64) * 1000, mask=np.zeros((len(points)), dtype=bool)
679680
)
680681
alphas = np.ma.MaskedArray(
681-
data=np.empty((len(points)), dtype=np.float64) * np.nan, mask=np.zeros((len(points)), dtype=bool)
682+
data=np.full((len(points)), np.nan, dtype=np.float64),
683+
mask=np.zeros((len(points)), dtype=bool)
682684
)
683685

684686
# use np.digitize to bin the depths into the layers.
@@ -771,14 +773,21 @@ def _apply_boundary_conditions(
771773
if bottom_boundary_condition == "mask":
772774
indices.mask[below_bottom_mask] = True
773775
alphas.mask[below_bottom_mask] = True
774-
776+
775777
indices.mask = np.logical_or(indices.mask, exclusion_mask)
776778
alphas.mask = np.logical_or(alphas.mask, exclusion_mask)
777779
return indices, alphas, oob_mask
778780

779-
def get_s_coordinate(self, points, time, data_shape=None, _hash=None, **kwargs):
781+
def get_s_coordinate(self,
782+
points,
783+
time,
784+
data_shape=None,
785+
_hash=None,
786+
**kwargs):
780787
"""
781-
Given an array of points and a time, returns the S-Coordinate values of the depth layers at those points and time.
788+
Given an array of points and a time, returns the S-Coordinate values
789+
of the depth layers at those points and time.
790+
782791
:param points: array of points to interpolate to
783792
:type points: numpy array of shape (n, 3)
784793
@@ -790,7 +799,8 @@ def get_s_coordinate(self, points, time, data_shape=None, _hash=None, **kwargs):
790799
index on the sigma layers or levels.
791800
:type data_shape: tuple of int
792801
793-
:return: numpy array of shape (n, num_w_levels) of n s-coordinate depth_profiles. 0 reference is mean sea surface.
802+
:return: numpy array of shape (n, num_w_levels) of n s-coordinate
803+
depth_profiles. 0 reference is mean sea surface.
794804
"""
795805
raise NotImplementedError("get_s_coordinate not implemented for S_Depth, required in subclasses")
796806

@@ -808,7 +818,8 @@ def get_depth_profile(self, points, time, data_shape=None, _hash=None, **kwargs)
808818
index on the sigma layers or levels.
809819
:type data_shape: tuple of int
810820
811-
:return: numpy array of shape (n, num_w_levels) of n depth_profiles, referenced to the surface (i.e. surface is 0, seafloor is negative)
821+
:return: numpy array of shape (n, num_w_levels) of n depth_profiles,
822+
referenced to the surface (i.e. surface is 0, seafloor is negative)
812823
"""
813824
z = self.zeta.at(points, time, unmask=False, _hash=_hash, **kwargs)
814825
return self.get_s_coordinate(points, time, data_shape=data_shape, _hash=_hash, **kwargs) + z

gridded/gridded.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def __init__(self, ncfile=None, grid=None, variables=None, grid_topology=None, a
7777
else: # Create from grid and variables -- this is what should usually happen.
7878
self.filename = None
7979
self.grid = grid
80+
self.time = None
81+
self.depth = None
8082
self.variables = {} if variables is None else variables
8183
self.attributes = {} if attributes is None else attributes
8284

@@ -93,7 +95,9 @@ def _init_from_netCDF(self, filename=None, grid_file=None, variable_files=None,
9395

9496
self.nc_dataset = get_dataset(filename)
9597
self.filename = self.nc_dataset.filepath()
96-
self.grid = Grid.from_netCDF(filename=self.filename, dataset=self.nc_dataset, grid_topology=grid_topology)
98+
self.grid = Grid.from_netCDF(filename=self.filename,
99+
dataset=self.nc_dataset,
100+
grid_topology=grid_topology)
97101
# fixme: this should load the depth and time, and then the variables.
98102
self.variables = self._variables_from_netCDF(self.nc_dataset)
99103
self.attributes = get_dataset_attrs(self.nc_dataset)
@@ -102,7 +106,7 @@ def _init_from_netCDF(self, filename=None, grid_file=None, variable_files=None,
102106
def from_netCDF(cls, filename=None, grid_file=None, variable_files=None, grid_topology=None):
103107
"""
104108
NOTE: only loading from a single file is currently implemented.
105-
you can create a DATaset by hand, by loading the grid and
109+
you can create a Dataset by hand, by loading the grid and
106110
variables separately, and then adding them
107111
108112
load a gridded.Dataset from a netCDF file

gridded/pysgrid/sgrid.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def info(self):
254254

255255
def get_all_face_padding(self):
256256
if self.face_padding is not None:
257-
all_face_padding = self.face_padding
257+
all_face_padding = self._face_padding
258258
else:
259259
all_face_padding = []
260260
return all_face_padding
@@ -270,7 +270,7 @@ def get_all_edge_padding(self):
270270
def all_padding(self):
271271
all_padding = self.get_all_face_padding() + self.get_all_edge_padding()
272272
if self.vertical_padding is not None:
273-
all_padding += self.vertical_padding
273+
all_padding += self._vertical_padding
274274
return all_padding
275275

276276
def save_as_netcdf(self, filepath):
@@ -313,9 +313,12 @@ def node_padding(self, val):
313313
self._node_padding = val
314314

315315
@property
316-
def center_padding(self):
317-
if hasattr(self, "_center_padding") and self._center_padding:
318-
return self._center_padding
316+
def face_padding(self):
317+
if hasattr(self, "_face_padding") and self._face_padding:
318+
if isinstance(self._face_padding[0], GridPadding):
319+
return (self._face_padding[0].padding, self._face_padding[1].padding)
320+
else:
321+
return self._face_padding
319322
elif hasattr(self, "center_lon") and self.center_lon is not None:
320323
face_shape = self.center_lon.shape
321324
node_shape = self.node_lon.shape
@@ -329,9 +332,17 @@ def center_padding(self):
329332
else:
330333
return (None, None)
331334

335+
@face_padding.setter
336+
def face_padding(self, val):
337+
self._face_padding = val
338+
339+
@property
340+
def center_padding(self):
341+
return self.face_padding
342+
332343
@center_padding.setter
333344
def center_padding(self, val):
334-
self._center_padding = val
345+
self._face_padding = val
335346

336347
@property
337348
def edge1_padding(self):
@@ -361,6 +372,20 @@ def edge2_padding(self):
361372
def edge2_padding(self, val):
362373
self._edge2_padding = val
363374

375+
@property
376+
def vertical_padding(self):
377+
if hasattr(self, "_vertical_padding") and self._vertical_padding:
378+
if isinstance(self._vertical_padding[0], GridPadding):
379+
return (None, self._vertical_padding[0].padding)
380+
else:
381+
return self._vertical_padding
382+
else:
383+
return (None, self.center_padding[1])
384+
385+
@vertical_padding.setter
386+
def vertical_padding(self, val):
387+
self._vertical_padding = val
388+
364389
def infer_location(self, variable):
365390
"""
366391
Assuming default is psi grid, check variable dimensions to determine which grid

0 commit comments

Comments
 (0)