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.
- roms_tools/_version.py +1 -1
- roms_tools/setup/boundary_forcing.py +253 -144
- roms_tools/setup/datasets.py +216 -48
- roms_tools/setup/download.py +13 -17
- roms_tools/setup/grid.py +561 -512
- roms_tools/setup/initial_conditions.py +148 -30
- roms_tools/setup/mask.py +69 -0
- roms_tools/setup/plot.py +4 -8
- roms_tools/setup/regrid.py +4 -2
- roms_tools/setup/surface_forcing.py +11 -18
- roms_tools/setup/tides.py +9 -12
- roms_tools/setup/topography.py +92 -128
- roms_tools/setup/utils.py +49 -25
- roms_tools/setup/vertical_coordinate.py +5 -16
- roms_tools/tests/test_setup/test_boundary_forcing.py +10 -5
- roms_tools/tests/test_setup/test_data/grid.zarr/.zattrs +0 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/.zmetadata +56 -201
- roms_tools/tests/test_setup/test_data/grid.zarr/Cs_r/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/Cs_w/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_rho → sigma_r}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_r/0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/{interface_depth_u → sigma_w}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/sigma_w/0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +1 -2
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +58 -203
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_r/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/Cs_w/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/{grid.zarr/interface_depth_v → grid_that_straddles_dateline.zarr/sigma_r}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_r/0 +0 -0
- roms_tools/tests/test_setup/test_data/{grid.zarr/layer_depth_rho → grid_that_straddles_dateline.zarr/sigma_w}/.zarray +2 -6
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/.zattrs +7 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/sigma_w/0 +0 -0
- roms_tools/tests/test_setup/test_grid.py +110 -12
- roms_tools/tests/test_setup/test_initial_conditions.py +2 -1
- roms_tools/tests/test_setup/test_river_forcing.py +3 -2
- roms_tools/tests/test_setup/test_surface_forcing.py +2 -22
- roms_tools/tests/test_setup/test_tides.py +2 -1
- roms_tools/tests/test_setup/test_topography.py +106 -1
- {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/LICENSE +1 -1
- {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/METADATA +2 -1
- {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/RECORD +52 -76
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/interface_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid.zarr/layer_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/interface_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_rho/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_u/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zarray +0 -24
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/.zattrs +0 -9
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/layer_depth_v/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_vertical_coordinate.py +0 -91
- {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/WHEEL +0 -0
- {roms_tools-1.7.0.dist-info → roms_tools-2.0.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/topography.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
30
|
-
topography_source : str
|
|
31
|
-
|
|
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
|
-
|
|
51
|
+
Updated dataset with added topography and metadata.
|
|
47
52
|
"""
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
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(
|
|
54
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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": "
|
|
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
|
|
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
|
-
|
|
98
|
+
kwargs = {"use_dask": False}
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
#
|
|
116
|
-
hraw =
|
|
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 =
|
|
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
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
737
|
-
|
|
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
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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)
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
bgc_boundary_forcing_from_climatology.plot(var_name="
|
|
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:
|
|
397
|
+
topography_source:
|
|
398
|
+
name: ETOPO5
|
|
394
399
|
hmin: 5.0
|
|
395
400
|
"""
|
|
396
401
|
)
|