roms-tools 1.7.0__py3-none-any.whl → 2.0.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 (85) hide show
  1. roms_tools/_version.py +1 -1
  2. roms_tools/setup/boundary_forcing.py +253 -144
  3. roms_tools/setup/datasets.py +216 -48
  4. roms_tools/setup/download.py +13 -17
  5. roms_tools/setup/grid.py +561 -512
  6. roms_tools/setup/initial_conditions.py +148 -30
  7. roms_tools/setup/mask.py +69 -0
  8. roms_tools/setup/plot.py +4 -8
  9. roms_tools/setup/regrid.py +4 -2
  10. roms_tools/setup/surface_forcing.py +11 -18
  11. roms_tools/setup/tides.py +9 -12
  12. roms_tools/setup/topography.py +92 -128
  13. roms_tools/setup/utils.py +49 -25
  14. roms_tools/setup/vertical_coordinate.py +5 -16
  15. roms_tools/tests/test_setup/test_boundary_forcing.py +10 -5
  16. roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +0 -1
  17. roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +56 -201
  18. roms_tools/tests/test_setup/test_data/grid.zarr/Cs_r/.zattrs +1 -1
  19. roms_tools/tests/test_setup/test_data/grid.zarr/Cs_w/.zattrs +1 -1
  20. roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_rho → sigma_r}/.zarray +2 -6
  21. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/.zattrs +7 -0
  22. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/0 +0 -0
  23. roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_u → sigma_w}/.zarray +2 -6
  24. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/.zattrs +7 -0
  25. roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/0 +0 -0
  26. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -2
  27. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +58 -203
  28. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_r/.zattrs +1 -1
  29. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_w/.zattrs +1 -1
  30. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/.zattrs +1 -1
  31. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
  32. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
  33. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
  34. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
  35. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
  36. roms_tools/tests/test_setup/test_data/{grid.zarr/interface_depth_v → grid_that_straddles_dateline.zarr/sigma_r}/.zarray +2 -6
  37. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/.zattrs +7 -0
  38. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/0 +0 -0
  39. roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_rho → grid_that_straddles_dateline.zarr/sigma_w}/.zarray +2 -6
  40. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/.zattrs +7 -0
  41. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/0 +0 -0
  42. roms_tools/tests/test_setup/test_grid.py +110 -12
  43. roms_tools/tests/test_setup/test_initial_conditions.py +2 -1
  44. roms_tools/tests/test_setup/test_river_forcing.py +3 -2
  45. roms_tools/tests/test_setup/test_surface_forcing.py +2 -22
  46. roms_tools/tests/test_setup/test_tides.py +2 -1
  47. roms_tools/tests/test_setup/test_topography.py +106 -1
  48. {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/LICENSE +1 -1
  49. {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/METADATA +2 -1
  50. {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/RECORD +52 -76
  51. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zattrs +0 -9
  52. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/0.0.0 +0 -0
  53. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/.zattrs +0 -9
  54. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/0.0.0 +0 -0
  55. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/.zattrs +0 -9
  56. roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/0.0.0 +0 -0
  57. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/.zattrs +0 -9
  58. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/0.0.0 +0 -0
  59. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zarray +0 -24
  60. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zattrs +0 -9
  61. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/0.0.0 +0 -0
  62. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zarray +0 -24
  63. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zattrs +0 -9
  64. roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/0.0.0 +0 -0
  65. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zarray +0 -24
  66. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zattrs +0 -9
  67. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/0.0.0 +0 -0
  68. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zarray +0 -24
  69. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zattrs +0 -9
  70. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/0.0.0 +0 -0
  71. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zarray +0 -24
  72. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zattrs +0 -9
  73. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/0.0.0 +0 -0
  74. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zarray +0 -24
  75. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zattrs +0 -9
  76. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/0.0.0 +0 -0
  77. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zarray +0 -24
  78. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zattrs +0 -9
  79. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/0.0.0 +0 -0
  80. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zarray +0 -24
  81. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zattrs +0 -9
  82. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/0.0.0 +0 -0
  83. roms_tools/tests/test_setup/test_vertical_coordinate.py +0 -91
  84. {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/WHEEL +0 -0
  85. {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,34 +1,37 @@
1
+ import time
2
+ import logging
1
3
  import xarray as xr
2
4
  import numpy as np
3
5
  import gcm_filters
4
- from scipy.interpolate import RegularGridInterpolator
5
- from scipy.ndimage import label
6
- from roms_tools.setup.download import fetch_topo
7
- from roms_tools.setup.utils import interpolate_from_rho_to_u, interpolate_from_rho_to_v
6
+ from roms_tools.setup.utils import handle_boundaries
8
7
  import warnings
9
8
  from itertools import count
10
-
11
-
12
- def _add_topography_and_mask(
13
- ds, topography_source, hmin, smooth_factor=8.0, rmax=0.2
9
+ from roms_tools.setup.datasets import ETOPO5Dataset, SRTM15Dataset
10
+ from roms_tools.setup.regrid import LateralRegrid
11
+
12
+
13
+ def _add_topography(
14
+ ds,
15
+ target_coords,
16
+ topography_source,
17
+ hmin,
18
+ smooth_factor=8.0,
19
+ rmax=0.2,
20
+ verbose=False,
14
21
  ) -> xr.Dataset:
15
- """Adds topography and a land/water mask to the dataset based on the provided
16
- topography source.
17
-
18
- This function performs the following operations:
19
- 1. Interpolates topography data onto the desired grid.
20
- 2. Applies a mask based on ocean depth.
21
- 3. Smooths the topography globally to reduce grid-scale instabilities.
22
- 4. Fills enclosed basins with land.
23
- 5. Smooths the topography locally to ensure the steepness ratio satisfies the rmax criterion.
24
- 6. Adds topography metadata.
22
+ """Adds topography to the dataset based on the provided topography source.
25
23
 
26
24
  Parameters
27
25
  ----------
28
26
  ds : xr.Dataset
29
- The dataset to which topography and the land/water mask will be added.
30
- topography_source : str
31
- The source of the topography data.
27
+ The dataset to which topography will be added.
28
+ topography_source : Dict[str, Union[str, Path]], optional
29
+ Dictionary specifying the source of the topography data:
30
+
31
+ - "name" (str): The name of the topography data source (e.g., "SRTM15").
32
+ - "path" (Union[str, Path, List[Union[str, Path]]]): The path to the raw data file. Can be a string or a Path object.
33
+
34
+ The default is "ETOPO5", which does not require a path.
32
35
  hmin : float
33
36
  The minimum allowable depth for the topography.
34
37
  smooth_factor : float, optional
@@ -39,81 +42,93 @@ def _add_topography_and_mask(
39
42
  The maximum allowable steepness ratio for the topography smoothing.
40
43
  This parameter controls the local smoothing of the topography. Smaller values result in
41
44
  smoother topography, while larger values preserve more detail. The default is 0.2.
45
+ verbose: bool, optional
46
+ Indicates whether to print topography generation steps with timing. Defaults to False.
42
47
 
43
48
  Returns
44
49
  -------
45
50
  xr.Dataset
46
- The dataset with added topography, mask, and metadata.
51
+ Updated dataset with added topography and metadata.
47
52
  """
48
53
 
49
- lon = ds.lon_rho.values
50
- lat = ds.lat_rho.values
54
+ if verbose:
55
+ start_time = time.time()
56
+ data = _get_topography_data(topography_source)
57
+ if verbose:
58
+ logging.info(
59
+ f"Reading the topography data: {time.time() - start_time:.3f} seconds"
60
+ )
51
61
 
52
62
  # interpolate topography onto desired grid
53
- hraw = _make_raw_topography(lon, lat, topography_source)
54
- hraw = xr.DataArray(data=hraw, dims=["eta_rho", "xi_rho"])
55
-
56
- # Mask is obtained by finding locations where ocean depth is positive
57
- mask = xr.where(hraw > 0, 1.0, 0.0)
63
+ hraw = _make_raw_topography(data, target_coords, verbose=verbose)
64
+ nan_check(hraw)
58
65
 
59
66
  # smooth topography domain-wide with Gaussian kernel to avoid grid scale instabilities
67
+ if verbose:
68
+ start_time = time.time()
60
69
  hraw = _smooth_topography_globally(hraw, smooth_factor)
61
-
62
- # fill enclosed basins with land
63
- mask = _fill_enclosed_basins(mask.values)
64
-
65
- # adjust mask boundaries by copying values from adjacent cells
66
- mask = _handle_boundaries(mask)
67
-
68
- ds["mask_rho"] = xr.DataArray(mask.astype(np.int32), dims=("eta_rho", "xi_rho"))
69
- ds["mask_rho"].attrs = {
70
- "long_name": "Mask at rho-points",
71
- "units": "land/water (0/1)",
72
- }
73
-
74
- ds = _add_velocity_masks(ds)
70
+ if verbose:
71
+ logging.info(
72
+ f"Smoothing the topography globally: {time.time() - start_time:.3f} seconds"
73
+ )
75
74
 
76
75
  # smooth topography locally to satisfy r < rmax
76
+ if verbose:
77
+ start_time = time.time()
78
+ # inserting hraw * mask_rho into this function eliminates any inconsistencies between
79
+ # the land according to the topography and the land according to the mask; land points
80
+ # will always be set to hmin
77
81
  ds["h"] = _smooth_topography_locally(hraw * ds["mask_rho"], hmin, rmax)
78
82
  ds["h"].attrs = {
79
- "long_name": "Final bathymetry at rho-points",
83
+ "long_name": "Bathymetry at rho-points",
80
84
  "units": "meter",
81
85
  }
86
+ if verbose:
87
+ logging.info(
88
+ f"Smoothing the topography locally: {time.time() - start_time:.3f} seconds"
89
+ )
82
90
 
83
91
  ds = _add_topography_metadata(ds, topography_source, smooth_factor, hmin, rmax)
84
92
 
85
93
  return ds
86
94
 
87
95
 
88
- def _make_raw_topography(lon, lat, topography_source) -> np.ndarray:
89
- """Given a grid of (lon, lat) points, fetch the topography file and interpolate
90
- height values onto the desired grid."""
96
+ def _get_topography_data(source):
91
97
 
92
- topo_ds = fetch_topo(topography_source)
98
+ kwargs = {"use_dask": False}
93
99
 
94
- # the following will depend on the topography source
95
- if topography_source == "ETOPO5":
96
- topo_lon = topo_ds["topo_lon"].copy()
97
- # Modify longitude values where necessary
98
- topo_lon = xr.where(topo_lon < 0, topo_lon + 360, topo_lon)
99
- topo_lon_minus360 = topo_lon - 360
100
- topo_lon_plus360 = topo_lon + 360
101
- # Concatenate along the longitude axis
102
- topo_lon_concatenated = xr.concat(
103
- [topo_lon_minus360, topo_lon, topo_lon_plus360], dim="lon"
104
- )
105
- topo_concatenated = xr.concat(
106
- [-topo_ds["topo"], -topo_ds["topo"], -topo_ds["topo"]], dim="lon"
100
+ if source["name"] == "ETOPO5":
101
+ if "path" in source.keys():
102
+ kwargs["filename"] = source["path"]
103
+ data = ETOPO5Dataset(**kwargs)
104
+ elif source["name"] == "SRTM15":
105
+ kwargs["filename"] = source["path"]
106
+ data = SRTM15Dataset(**kwargs)
107
+ else:
108
+ raise ValueError(
109
+ 'Only "ETOPO5" and "SRTM15" are valid options for topography_source["name"].'
107
110
  )
108
111
 
109
- interp = RegularGridInterpolator(
110
- (topo_ds["topo_lat"].values, topo_lon_concatenated.values),
111
- topo_concatenated.values,
112
- method="linear",
112
+ return data
113
+
114
+
115
+ def _make_raw_topography(
116
+ data, target_coords, method="linear", verbose=False
117
+ ) -> xr.DataArray:
118
+
119
+ data.choose_subdomain(target_coords, buffer_points=3, verbose=verbose)
120
+
121
+ if verbose:
122
+ start_time = time.time()
123
+ lateral_regrid = LateralRegrid(target_coords, data.dim_names)
124
+ hraw = lateral_regrid.apply(data.ds[data.var_names["topo"]], method=method)
125
+ if verbose:
126
+ logging.info(
127
+ f"Regridding the topography: {time.time() - start_time:.3f} seconds"
113
128
  )
114
129
 
115
- # Interpolate onto desired domain grid points
116
- hraw = interp((lat, lon))
130
+ # flip sign so that bathmetry is positive
131
+ hraw = -hraw
117
132
 
118
133
  return hraw
119
134
 
@@ -148,28 +163,6 @@ def _smooth_topography_globally(hraw, factor) -> xr.DataArray:
148
163
  return hsmooth
149
164
 
150
165
 
151
- def _fill_enclosed_basins(mask) -> np.ndarray:
152
- """Fills in enclosed basins with land."""
153
-
154
- # Label connected regions in the mask
155
- reg, nreg = label(mask)
156
- # Find the largest region
157
- lint = 0
158
- lreg = 0
159
- for ireg in range(nreg):
160
- int_ = np.sum(reg == ireg)
161
- if int_ > lint and mask[reg == ireg].sum() > 0:
162
- lreg = ireg
163
- lint = int_
164
-
165
- # Remove regions other than the largest one
166
- for ireg in range(nreg):
167
- if ireg != lreg:
168
- mask[reg == ireg] = 0
169
-
170
- return mask
171
-
172
-
173
166
  def _smooth_topography_locally(h, hmin=5, rmax=0.2):
174
167
  """Smoothes topography locally to satisfy r < rmax."""
175
168
  # Compute rmax_log
@@ -232,7 +225,7 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
232
225
  )
233
226
 
234
227
  # No gradient at the domain boundaries
235
- h_log = _handle_boundaries(h_log)
228
+ h_log = handle_boundaries(h_log)
236
229
 
237
230
  # Update h
238
231
  h = hmin * np.exp(h_log)
@@ -248,29 +241,6 @@ def _smooth_topography_locally(h, hmin=5, rmax=0.2):
248
241
  return h
249
242
 
250
243
 
251
- def _handle_boundaries(field):
252
- """Adjust the boundaries of a 2D field by copying values from adjacent cells.
253
-
254
- Parameters
255
- ----------
256
- field : numpy.ndarray or xarray.DataArray
257
- A 2D array representing a field (e.g., topography or mask) whose boundary values
258
- need to be adjusted.
259
-
260
- Returns
261
- -------
262
- field : numpy.ndarray or xarray.DataArray
263
- The input field with adjusted boundary values.
264
- """
265
-
266
- field[0, :] = field[1, :]
267
- field[-1, :] = field[-2, :]
268
- field[:, 0] = field[:, 1]
269
- field[:, -1] = field[:, -2]
270
-
271
- return field
272
-
273
-
274
244
  def _compute_rfactor(h):
275
245
  """Computes slope parameter (or r-factor) r = |Delta h| / 2h in both horizontal grid
276
246
  directions."""
@@ -286,23 +256,17 @@ def _compute_rfactor(h):
286
256
 
287
257
 
288
258
  def _add_topography_metadata(ds, topography_source, smooth_factor, hmin, rmax):
289
- ds.attrs["topography_source"] = topography_source
259
+ ds.attrs["topography_source"] = topography_source["name"]
290
260
  ds.attrs["hmin"] = hmin
291
261
 
292
262
  return ds
293
263
 
294
264
 
295
- def _add_velocity_masks(ds):
296
-
297
- # add u- and v-masks
298
- ds["mask_u"] = interpolate_from_rho_to_u(
299
- ds["mask_rho"], method="multiplicative"
300
- ).astype(np.int32)
301
- ds["mask_v"] = interpolate_from_rho_to_v(
302
- ds["mask_rho"], method="multiplicative"
303
- ).astype(np.int32)
304
-
305
- ds["mask_u"].attrs = {"long_name": "Mask at u-points", "units": "land/water (0/1)"}
306
- ds["mask_v"].attrs = {"long_name": "Mask at v-points", "units": "land/water (0/1)"}
307
-
308
- return ds
265
+ def nan_check(hraw):
266
+ error_message = (
267
+ "NaN values found in regridded topography. This likely occurs because the ROMS grid, including "
268
+ "a small safety margin for interpolation, is not fully contained within the topography dataset's longitude/latitude range. Please ensure that the "
269
+ "dataset covers the entire area required by the ROMS grid."
270
+ )
271
+ if hraw.isnull().any().values:
272
+ raise ValueError(error_message)
roms_tools/setup/utils.py CHANGED
@@ -41,7 +41,7 @@ def nan_check(field, mask, error_message=None) -> None:
41
41
  da = xr.where(mask == 1, field, 0)
42
42
  if error_message is None:
43
43
  error_message = (
44
- "NaN values found in interpolated field. This likely occurs because the ROMS grid, including "
44
+ "NaN values found in regridded field. This likely occurs because the ROMS grid, including "
45
45
  "a small safety margin for interpolation, is not fully contained within the dataset's longitude/latitude range. Please ensure that the "
46
46
  "dataset covers the entire area required by the ROMS grid."
47
47
  )
@@ -107,10 +107,10 @@ def interpolate_from_rho_to_u(field, method="additive"):
107
107
  else:
108
108
  raise NotImplementedError(f"Unsupported method '{method}' specified.")
109
109
 
110
- if "lat_rho" in field_interpolated.coords:
111
- field_interpolated.drop_vars(["lat_rho"])
112
- if "lon_rho" in field_interpolated.coords:
113
- field_interpolated.drop_vars(["lon_rho"])
110
+ vars_to_drop = ["lat_rho", "lon_rho", "eta_rho", "xi_rho"]
111
+ for var in vars_to_drop:
112
+ if var in field_interpolated.coords:
113
+ field_interpolated = field_interpolated.drop_vars(var)
114
114
 
115
115
  field_interpolated = field_interpolated.swap_dims({"xi_rho": "xi_u"})
116
116
 
@@ -155,10 +155,10 @@ def interpolate_from_rho_to_v(field, method="additive"):
155
155
  else:
156
156
  raise NotImplementedError(f"Unsupported method '{method}' specified.")
157
157
 
158
- if "lat_rho" in field_interpolated.coords:
159
- field_interpolated.drop_vars(["lat_rho"])
160
- if "lon_rho" in field_interpolated.coords:
161
- field_interpolated.drop_vars(["lon_rho"])
158
+ vars_to_drop = ["lat_rho", "lon_rho", "eta_rho", "xi_rho"]
159
+ for var in vars_to_drop:
160
+ if var in field_interpolated.coords:
161
+ field_interpolated = field_interpolated.drop_vars(var)
162
162
 
163
163
  field_interpolated = field_interpolated.swap_dims({"eta_rho": "eta_v"})
164
164
 
@@ -733,27 +733,27 @@ def get_target_coords(grid, use_coarse_grid=False):
733
733
  """
734
734
  # Select grid variables based on whether the coarse grid is used
735
735
  if use_coarse_grid:
736
- lat, lon, angle, mask = (
737
- grid.ds.lat_coarse.rename({"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}),
738
- grid.ds.lon_coarse.rename({"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}),
739
- grid.ds.angle_coarse.rename(
740
- {"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}
741
- ),
742
- grid.ds.mask_coarse.rename(
743
- {"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}
744
- ),
736
+ lat = grid.ds.lat_coarse.rename(
737
+ {"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}
745
738
  )
739
+ lon = grid.ds.lon_coarse.rename(
740
+ {"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}
741
+ )
742
+ angle = grid.ds.angle_coarse.rename(
743
+ {"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"}
744
+ )
745
+ mask = grid.ds.get("mask_coarse")
746
+ if mask is not None:
747
+ mask = mask.rename({"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"})
746
748
 
747
749
  lat_psi = grid.ds.get("lat_psi_coarse")
748
750
  lon_psi = grid.ds.get("lon_psi_coarse")
749
751
 
750
752
  else:
751
- lat, lon, angle, mask = (
752
- grid.ds.lat_rho,
753
- grid.ds.lon_rho,
754
- grid.ds.angle,
755
- grid.ds.mask_rho,
756
- )
753
+ lat = grid.ds.lat_rho
754
+ lon = grid.ds.lon_rho
755
+ angle = grid.ds.angle
756
+ mask = grid.ds.get("mask_rho")
757
757
  lat_psi = grid.ds.get("lat_psi")
758
758
  lon_psi = grid.ds.get("lon_psi")
759
759
 
@@ -1061,7 +1061,8 @@ def _to_yaml(forcing_object, filepath: Union[str, Path]) -> None:
1061
1061
  # Convert the grid attribute to a dictionary and remove non-serializable fields
1062
1062
  grid_data = asdict(forcing_object.grid)
1063
1063
  grid_data.pop("ds", None) # Remove 'ds' attribute (non-serializable)
1064
- grid_data.pop("straddle", None) # Remove 'straddle' if it's non-essential
1064
+ grid_data.pop("straddle", None)
1065
+ grid_data.pop("verbose", None)
1065
1066
  grid_yaml_data = {"Grid": grid_data}
1066
1067
 
1067
1068
  # Step 2: Get ROMS Tools version
@@ -1179,3 +1180,26 @@ def _convert_from_iso_format(value):
1179
1180
  except ValueError:
1180
1181
  # Return None or raise an exception if parsing fails
1181
1182
  return value
1183
+
1184
+
1185
+ def handle_boundaries(field):
1186
+ """Adjust the boundaries of a 2D field by copying values from adjacent cells.
1187
+
1188
+ Parameters
1189
+ ----------
1190
+ field : numpy.ndarray or xarray.DataArray
1191
+ A 2D array representing a field (e.g., topography or mask) whose boundary values
1192
+ need to be adjusted.
1193
+
1194
+ Returns
1195
+ -------
1196
+ field : numpy.ndarray or xarray.DataArray
1197
+ The input field with adjusted boundary values.
1198
+ """
1199
+
1200
+ field[0, :] = field[1, :]
1201
+ field[-1, :] = field[-2, :]
1202
+ field[:, 0] = field[:, 1]
1203
+ field[:, -1] = field[:, -2]
1204
+
1205
+ return field
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
  import xarray as xr
3
+ from roms_tools.setup.utils import transpose_dimensions
3
4
 
4
5
 
5
6
  def compute_cs(sigma, theta_s, theta_b):
@@ -83,7 +84,7 @@ def compute_depth(zeta, h, hc, cs, sigma):
83
84
 
84
85
  Parameters
85
86
  ----------
86
- zeta : xr.DataArray
87
+ zeta : xr.DataArray or scalar
87
88
  The sea surface height.
88
89
  h : xr.DataArray
89
90
  The depth of the sea bottom.
@@ -98,23 +99,11 @@ def compute_depth(zeta, h, hc, cs, sigma):
98
99
  -------
99
100
  z : xr.DataArray
100
101
  The depth at different sigma levels.
101
-
102
- Raises
103
- ------
104
- ValueError
105
- If theta_s or theta_b are less than or equal to zero.
106
102
  """
107
103
 
108
- # Expand dimensions
109
- sigma = sigma.expand_dims(dim={"eta_rho": h.eta_rho, "xi_rho": h.xi_rho})
110
- cs = cs.expand_dims(dim={"eta_rho": h.eta_rho, "xi_rho": h.xi_rho})
111
-
112
- s = (hc * sigma + h * cs) / (hc + h)
113
- z = zeta + (zeta + h) * s
104
+ z = (hc * sigma + h * cs) / (hc + h)
105
+ z = zeta + (zeta + h) * z
114
106
 
115
- if "s_rho" in z.dims:
116
- z = z.transpose("s_rho", "eta_rho", "xi_rho")
117
- elif "s_w" in z.dims:
118
- z = z.transpose("s_w", "eta_rho", "xi_rho")
107
+ z = -transpose_dimensions(z)
119
108
 
120
109
  return z
@@ -241,10 +241,14 @@ def test_boundary_forcing_save(boundary_forcing, tmp_path):
241
241
  def test_bgc_boundary_forcing_plot(bgc_boundary_forcing_from_climatology):
242
242
  """Test plot method."""
243
243
 
244
- bgc_boundary_forcing_from_climatology.plot(var_name="ALK_south")
245
- bgc_boundary_forcing_from_climatology.plot(var_name="ALK_east")
246
- bgc_boundary_forcing_from_climatology.plot(var_name="ALK_north")
247
- bgc_boundary_forcing_from_climatology.plot(var_name="ALK_west")
244
+ bgc_boundary_forcing_from_climatology.plot(
245
+ var_name="ALK_south", layer_contours=True
246
+ )
247
+ bgc_boundary_forcing_from_climatology.plot(var_name="ALK_east", layer_contours=True)
248
+ bgc_boundary_forcing_from_climatology.plot(
249
+ var_name="ALK_north", layer_contours=True
250
+ )
251
+ bgc_boundary_forcing_from_climatology.plot(var_name="ALK_west", layer_contours=True)
248
252
 
249
253
 
250
254
  def test_bgc_boundary_forcing_save(bgc_boundary_forcing_from_climatology, tmp_path):
@@ -390,7 +394,8 @@ def test_from_yaml_missing_boundary_forcing(tmp_path, request, use_dask):
390
394
  center_lon: -10
391
395
  center_lat: 61
392
396
  rot: -20
393
- topography_source: ETOPO5
397
+ topography_source:
398
+ name: ETOPO5
394
399
  hmin: 5.0
395
400
  """
396
401
  )
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "center_lat": 0,
3
3
  "center_lon": -20,
4
- "coordinates": "interface_depth_rho interface_depth_u interface_depth_v layer_depth_rho layer_depth_u layer_depth_v",
5
4
  "hc": 300.0,
6
5
  "hmin": 5.0,
7
6
  "roms_tools_version": "0.1.dev157+dirty",