roms-tools 2.0.0__py3-none-any.whl → 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. roms_tools/__init__.py +2 -1
  2. roms_tools/setup/boundary_forcing.py +22 -32
  3. roms_tools/setup/datasets.py +19 -21
  4. roms_tools/setup/grid.py +253 -139
  5. roms_tools/setup/initial_conditions.py +29 -6
  6. roms_tools/setup/mask.py +50 -4
  7. roms_tools/setup/nesting.py +575 -0
  8. roms_tools/setup/plot.py +214 -55
  9. roms_tools/setup/river_forcing.py +125 -29
  10. roms_tools/setup/surface_forcing.py +33 -12
  11. roms_tools/setup/tides.py +31 -6
  12. roms_tools/setup/topography.py +168 -35
  13. roms_tools/setup/utils.py +137 -21
  14. roms_tools/tests/test_setup/test_boundary_forcing.py +7 -5
  15. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +2 -3
  16. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +1 -2
  17. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/.zarray +1 -1
  18. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_name/0 +0 -0
  19. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zmetadata +5 -6
  20. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_tracer/.zarray +2 -2
  21. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_tracer/.zattrs +1 -2
  22. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_tracer/0.0.0 +0 -0
  23. roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/tracer_name/.zarray +2 -2
  24. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_name/0 +0 -0
  25. roms_tools/tests/test_setup/test_datasets.py +2 -2
  26. roms_tools/tests/test_setup/test_initial_conditions.py +6 -6
  27. roms_tools/tests/test_setup/test_nesting.py +489 -0
  28. roms_tools/tests/test_setup/test_river_forcing.py +50 -13
  29. roms_tools/tests/test_setup/test_surface_forcing.py +9 -8
  30. roms_tools/tests/test_setup/test_tides.py +5 -5
  31. roms_tools/tests/test_setup/test_validation.py +2 -2
  32. {roms_tools-2.0.0.dist-info → roms_tools-2.2.0.dist-info}/METADATA +9 -5
  33. {roms_tools-2.0.0.dist-info → roms_tools-2.2.0.dist-info}/RECORD +54 -53
  34. {roms_tools-2.0.0.dist-info → roms_tools-2.2.0.dist-info}/WHEEL +1 -1
  35. roms_tools/_version.py +0 -2
  36. roms_tools/tests/test_setup/test_data/river_forcing.zarr/river_tracer/0.0.0 +0 -0
  37. roms_tools/tests/test_setup/test_data/river_forcing.zarr/tracer_name/0 +0 -0
  38. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zattrs +0 -0
  39. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/.zgroup +0 -0
  40. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/.zarray +0 -0
  41. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/.zattrs +0 -0
  42. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/abs_time/0 +0 -0
  43. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/.zarray +0 -0
  44. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/.zattrs +0 -0
  45. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/month/0 +0 -0
  46. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/.zarray +0 -0
  47. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/.zattrs +0 -0
  48. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_name/0 +0 -0
  49. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/.zarray +0 -0
  50. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/.zattrs +0 -0
  51. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_time/0 +0 -0
  52. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/.zarray +0 -0
  53. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/.zattrs +0 -0
  54. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/river_volume/0.0 +0 -0
  55. /roms_tools/tests/test_setup/test_data/{river_forcing.zarr → river_forcing_with_bgc.zarr}/tracer_name/.zattrs +0 -0
  56. {roms_tools-2.0.0.dist-info → roms_tools-2.2.0.dist-info}/LICENSE +0 -0
  57. {roms_tools-2.0.0.dist-info → roms_tools-2.2.0.dist-info}/top_level.txt +0 -0
roms_tools/__init__.py CHANGED
@@ -5,7 +5,7 @@ try:
5
5
  __version__ = _version("roms_tools")
6
6
  except ImportError: # pragma: no cover
7
7
  # Local copy or not installed with setuptools
8
- __version__ = "999"
8
+ __version__ = "9999"
9
9
 
10
10
 
11
11
  from roms_tools.setup.grid import Grid # noqa: F401
@@ -14,6 +14,7 @@ from roms_tools.setup.surface_forcing import SurfaceForcing # noqa: F401
14
14
  from roms_tools.setup.initial_conditions import InitialConditions # noqa: F401
15
15
  from roms_tools.setup.boundary_forcing import BoundaryForcing # noqa: F401
16
16
  from roms_tools.setup.river_forcing import RiverForcing # noqa: F401
17
+ from roms_tools.setup.nesting import Nesting # noqa: F401
17
18
 
18
19
  # Configure logging when the package is imported
19
20
  logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
@@ -24,6 +24,7 @@ from roms_tools.setup.utils import (
24
24
  interpolate_from_rho_to_u,
25
25
  interpolate_from_rho_to_v,
26
26
  convert_to_roms_time,
27
+ get_boundary_coords,
27
28
  _to_yaml,
28
29
  _from_yaml,
29
30
  )
@@ -71,6 +72,10 @@ class BoundaryForcing:
71
72
  Reference date for the model. Default is January 1, 2000.
72
73
  use_dask: bool, optional
73
74
  Indicates whether to use dask for processing. If True, data is processed with dask; if False, data is processed eagerly. Defaults to False.
75
+ bypass_validation: bool, optional
76
+ Indicates whether to skip validation checks in the processed data. When set to True,
77
+ the validation process that ensures no NaN values exist at wet points
78
+ in the processed dataset is bypassed. Defaults to False.
74
79
 
75
80
  Examples
76
81
  --------
@@ -100,6 +105,7 @@ class BoundaryForcing:
100
105
  apply_2d_horizontal_fill: bool = False
101
106
  model_reference_date: datetime = datetime(2000, 1, 1)
102
107
  use_dask: bool = False
108
+ bypass_validation: bool = False
103
109
 
104
110
  ds: xr.Dataset = field(init=False, repr=False)
105
111
 
@@ -279,7 +285,8 @@ class BoundaryForcing:
279
285
  # Add global information
280
286
  ds = self._add_global_metadata(data, ds)
281
287
 
282
- self._validate(ds)
288
+ if not self.bypass_validation:
289
+ self._validate(ds)
283
290
 
284
291
  # substitute NaNs over land by a fill value to avoid blow-up of ROMS
285
292
  for var_name in ds.data_vars:
@@ -459,7 +466,7 @@ class BoundaryForcing:
459
466
  return ds
460
467
 
461
468
  def _set_boundary_info(self):
462
- """Updates boundary coordinates for rho, u, and v variables on the grid.
469
+ """Sets boundary coordinates for rho, u, and v variables on the grid.
463
470
 
464
471
  This method determines the boundary points for the grid variables by specifying the
465
472
  indices for the south, east, north, and west boundaries. The resulting boundary
@@ -476,32 +483,7 @@ class BoundaryForcing:
476
483
  grid indices for the respective variable types.
477
484
  """
478
485
 
479
- bdry_coords = {
480
- "rho": {
481
- "south": {"eta_rho": 0},
482
- "east": {"xi_rho": -1},
483
- "north": {"eta_rho": -1},
484
- "west": {"xi_rho": 0},
485
- },
486
- "u": {
487
- "south": {"eta_rho": 0},
488
- "east": {"xi_u": -1},
489
- "north": {"eta_rho": -1},
490
- "west": {"xi_u": 0},
491
- },
492
- "v": {
493
- "south": {"eta_v": 0},
494
- "east": {"xi_rho": -1},
495
- "north": {"eta_v": -1},
496
- "west": {"xi_rho": 0},
497
- },
498
- "vector": {
499
- "south": {"eta_rho": [0, 1]},
500
- "east": {"xi_rho": [-2, -1]},
501
- "north": {"eta_rho": [-2, -1]},
502
- "west": {"xi_rho": [0, 1]},
503
- },
504
- }
486
+ bdry_coords = get_boundary_coords()
505
487
 
506
488
  object.__setattr__(self, "bdry_coords", bdry_coords)
507
489
 
@@ -728,8 +710,7 @@ class BoundaryForcing:
728
710
  variable in the dataset.
729
711
  """
730
712
  for var_name in self.variable_info:
731
- # only validate variables based on "validate" flag if use_dask is false
732
- if not self.use_dask or self.variable_info[var_name]["validate"]:
713
+ if self.variable_info[var_name]["validate"]:
733
714
  location = self.variable_info[var_name]["location"]
734
715
 
735
716
  # Select the appropriate mask based on variable location
@@ -980,7 +961,10 @@ class BoundaryForcing:
980
961
 
981
962
  @classmethod
982
963
  def from_yaml(
983
- cls, filepath: Union[str, Path], use_dask: bool = False
964
+ cls,
965
+ filepath: Union[str, Path],
966
+ use_dask: bool = False,
967
+ bypass_validation: bool = False,
984
968
  ) -> "BoundaryForcing":
985
969
  """Create an instance of the BoundaryForcing class from a YAML file.
986
970
 
@@ -990,6 +974,10 @@ class BoundaryForcing:
990
974
  The path to the YAML file from which the parameters will be read.
991
975
  use_dask: bool, optional
992
976
  Indicates whether to use dask for processing. If True, data is processed with dask; if False, data is processed eagerly. Defaults to False.
977
+ bypass_validation: bool, optional
978
+ Indicates whether to skip validation checks in the processed data. When set to True,
979
+ the validation process that ensures no NaN values exist at wet points
980
+ in the processed dataset is bypassed. Defaults to False.
993
981
 
994
982
  Returns
995
983
  -------
@@ -1002,7 +990,9 @@ class BoundaryForcing:
1002
990
  params = _from_yaml(cls, filepath)
1003
991
 
1004
992
  # Create and return an instance of InitialConditions
1005
- return cls(grid=grid, **params, use_dask=use_dask)
993
+ return cls(
994
+ grid=grid, **params, use_dask=use_dask, bypass_validation=bypass_validation
995
+ )
1006
996
 
1007
997
 
1008
998
  def apply_1d_horizontal_fill(processed_fields: dict) -> dict:
@@ -1369,7 +1369,7 @@ class ERA5Correction(Dataset):
1369
1369
 
1370
1370
  super().__post_init__()
1371
1371
 
1372
- def choose_subdomain(self, coords, straddle: bool):
1372
+ def choose_subdomain(self, target_coords, straddle: bool):
1373
1373
  """Converts longitude values in the dataset if necessary and selects a subdomain
1374
1374
  based on the specified coordinates.
1375
1375
 
@@ -1378,7 +1378,7 @@ class ERA5Correction(Dataset):
1378
1378
 
1379
1379
  Parameters
1380
1380
  ----------
1381
- coords : dict
1381
+ target_coords : dict
1382
1382
  A dictionary specifying the target coordinates for selecting the subdomain. Keys should correspond to the
1383
1383
  dimension names of the dataset (e.g., latitude and longitude), and values should be the desired ranges or
1384
1384
  specific coordinate values.
@@ -1397,32 +1397,24 @@ class ERA5Correction(Dataset):
1397
1397
  - The dataset (`self.ds`) is updated in place to reflect the chosen subdomain.
1398
1398
  """
1399
1399
 
1400
- lon = self.ds[self.dim_names["longitude"]]
1401
-
1402
- if not self.is_global:
1403
- if lon.min().values < 0 and not straddle:
1404
- # Convert from [-180, 180] to [0, 360]
1405
- self.ds[self.dim_names["longitude"]] = xr.where(lon < 0, lon + 360, lon)
1400
+ # Select the subdomain in latitude direction (so that we have to concatenate fewer latitudes below if concatenation is performed)
1401
+ subdomain = self.ds.sel({self.dim_names["latitude"]: target_coords["lat"]})
1406
1402
 
1407
- if lon.max().values > 180 and straddle:
1408
- # Convert from [0, 360] to [-180, 180]
1409
- self.ds[self.dim_names["longitude"]] = xr.where(
1410
- lon > 180, lon - 360, lon
1411
- )
1403
+ if self.is_global:
1404
+ # Always concatenate because computational overhead should be managable for 1/4 degree ERA5 resolution
1405
+ subdomain = self.concatenate_longitudes(
1406
+ subdomain, end="both", verbose=False
1407
+ )
1412
1408
 
1413
- # Select the subdomain based on the specified latitude and longitude ranges
1414
- subdomain = self.ds.sel(**coords)
1409
+ # Select the subdomain in longitude direction
1410
+ subdomain = subdomain.sel({self.dim_names["longitude"]: target_coords["lon"]})
1415
1411
 
1416
1412
  # Check if the selected subdomain contains the specified latitude and longitude values
1417
- if not subdomain[self.dim_names["latitude"]].equals(
1418
- coords[self.dim_names["latitude"]]
1419
- ):
1413
+ if not subdomain[self.dim_names["latitude"]].equals(target_coords["lat"]):
1420
1414
  raise ValueError(
1421
1415
  "The correction dataset does not contain all specified latitude values."
1422
1416
  )
1423
- if not subdomain[self.dim_names["longitude"]].equals(
1424
- coords[self.dim_names["longitude"]]
1425
- ):
1417
+ if not subdomain[self.dim_names["longitude"]].equals(target_coords["lon"]):
1426
1418
  raise ValueError(
1427
1419
  "The correction dataset does not contain all specified longitude values."
1428
1420
  )
@@ -2060,6 +2052,12 @@ def _load_data(filename, dim_names, use_dask, decode_times=True):
2060
2052
  **combine_kwargs,
2061
2053
  **kwargs,
2062
2054
  )
2055
+
2056
+ # Rechunk the dataset along the tidal constituent dimension ("ntides") after loading
2057
+ # because the original dataset does not have a chunk size of 1 along this dimension.
2058
+ if "ntides" in dim_names:
2059
+ ds = ds.chunk({dim_names["ntides"]: 1})
2060
+
2063
2061
  else:
2064
2062
  ds_list = []
2065
2063
  for file in matching_files: