roms-tools 0.0.6__py3-none-any.whl → 0.1.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.
- ci/environment.yml +28 -0
- roms_tools/__init__.py +3 -0
- roms_tools/_version.py +1 -1
- roms_tools/setup/atmospheric_forcing.py +993 -0
- roms_tools/setup/datasets.py +48 -0
- roms_tools/setup/fill.py +263 -0
- roms_tools/setup/grid.py +483 -324
- roms_tools/setup/plot.py +58 -0
- roms_tools/setup/tides.py +676 -0
- roms_tools/setup/topography.py +242 -0
- roms_tools/tests/test_setup.py +145 -18
- roms_tools-0.1.0.dist-info/METADATA +89 -0
- roms_tools-0.1.0.dist-info/RECORD +17 -0
- {roms_tools-0.0.6.dist-info → roms_tools-0.1.0.dist-info}/WHEEL +1 -1
- {roms_tools-0.0.6.dist-info → roms_tools-0.1.0.dist-info}/top_level.txt +1 -0
- roms_tools-0.0.6.dist-info/METADATA +0 -134
- roms_tools-0.0.6.dist-info/RECORD +0 -10
- {roms_tools-0.0.6.dist-info → roms_tools-0.1.0.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import xarray as xr
|
|
2
|
+
import numpy as np
|
|
3
|
+
import gcm_filters
|
|
4
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
5
|
+
from scipy.ndimage import label
|
|
6
|
+
from roms_tools.setup.datasets import fetch_topo
|
|
7
|
+
import warnings
|
|
8
|
+
from itertools import count
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _add_topography_and_mask(
|
|
12
|
+
ds, topography_source, smooth_factor, hmin, rmax
|
|
13
|
+
) -> xr.Dataset:
|
|
14
|
+
lon = ds.lon_rho.values
|
|
15
|
+
lat = ds.lat_rho.values
|
|
16
|
+
|
|
17
|
+
# interpolate topography onto desired grid
|
|
18
|
+
hraw = _make_raw_topography(lon, lat, topography_source)
|
|
19
|
+
hraw = xr.DataArray(data=hraw, dims=["eta_rho", "xi_rho"])
|
|
20
|
+
|
|
21
|
+
# Mask is obtained by finding locations where ocean depth is positive
|
|
22
|
+
mask = xr.where(hraw > 0, 1, 0)
|
|
23
|
+
|
|
24
|
+
# smooth topography domain-wide with Gaussian kernel to avoid grid scale instabilities
|
|
25
|
+
ds["hraw"] = _smooth_topography_globally(hraw, mask, smooth_factor)
|
|
26
|
+
ds["hraw"].attrs = {
|
|
27
|
+
"long_name": "Working bathymetry at rho-points",
|
|
28
|
+
"source": f"Raw bathymetry from {topography_source} (smoothing diameter {smooth_factor})",
|
|
29
|
+
"units": "meter",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# fill enclosed basins with land
|
|
33
|
+
mask = _fill_enclosed_basins(mask.values)
|
|
34
|
+
ds["mask_rho"] = xr.DataArray(mask, dims=("eta_rho", "xi_rho"))
|
|
35
|
+
ds["mask_rho"].attrs = {
|
|
36
|
+
"long_name": "Mask at rho-points",
|
|
37
|
+
"units": "land/water (0/1)",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# smooth topography locally to satisfy r < rmax
|
|
41
|
+
ds["h"] = _smooth_topography_locally(ds["hraw"] * ds["mask_rho"], hmin, rmax)
|
|
42
|
+
ds["h"].attrs = {
|
|
43
|
+
"long_name": "Final bathymetry at rho-points",
|
|
44
|
+
"units": "meter",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ds = _add_topography_metadata(ds, topography_source, smooth_factor, hmin, rmax)
|
|
48
|
+
|
|
49
|
+
return ds
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _make_raw_topography(lon, lat, topography_source) -> np.ndarray:
|
|
53
|
+
"""
|
|
54
|
+
Given a grid of (lon, lat) points, fetch the topography file and interpolate height values onto the desired grid.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
topo_ds = fetch_topo(topography_source)
|
|
58
|
+
|
|
59
|
+
# the following will depend on the topography source
|
|
60
|
+
if topography_source == "etopo5":
|
|
61
|
+
topo_lon = topo_ds["topo_lon"].copy()
|
|
62
|
+
# Modify longitude values where necessary
|
|
63
|
+
topo_lon = xr.where(topo_lon < 0, topo_lon + 360, topo_lon)
|
|
64
|
+
topo_lon_minus360 = topo_lon - 360
|
|
65
|
+
topo_lon_plus360 = topo_lon + 360
|
|
66
|
+
# Concatenate along the longitude axis
|
|
67
|
+
topo_lon_concatenated = xr.concat(
|
|
68
|
+
[topo_lon_minus360, topo_lon, topo_lon_plus360], dim="lon"
|
|
69
|
+
)
|
|
70
|
+
topo_concatenated = xr.concat(
|
|
71
|
+
[-topo_ds["topo"], -topo_ds["topo"], -topo_ds["topo"]], dim="lon"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
interp = RegularGridInterpolator(
|
|
75
|
+
(topo_ds["topo_lat"].values, topo_lon_concatenated.values),
|
|
76
|
+
topo_concatenated.values,
|
|
77
|
+
method="linear",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Interpolate onto desired domain grid points
|
|
81
|
+
hraw = interp((lat, lon))
|
|
82
|
+
|
|
83
|
+
return hraw
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _smooth_topography_globally(hraw, wet_mask, factor) -> xr.DataArray:
|
|
87
|
+
# since GCM-Filters assumes periodic domain, we extend the domain by one grid cell in each dimension
|
|
88
|
+
# and set that margin to land
|
|
89
|
+
margin_mask = xr.concat([wet_mask, 0 * wet_mask.isel(eta_rho=-1)], dim="eta_rho")
|
|
90
|
+
margin_mask = xr.concat(
|
|
91
|
+
[margin_mask, 0 * margin_mask.isel(xi_rho=-1)], dim="xi_rho"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# we choose a Gaussian filter kernel corresponding to a Gaussian with standard deviation factor/sqrt(12);
|
|
95
|
+
# this standard deviation matches the standard deviation of a boxcar kernel with total width equal to factor.
|
|
96
|
+
filter = gcm_filters.Filter(
|
|
97
|
+
filter_scale=factor,
|
|
98
|
+
dx_min=1,
|
|
99
|
+
filter_shape=gcm_filters.FilterShape.GAUSSIAN,
|
|
100
|
+
grid_type=gcm_filters.GridType.REGULAR_WITH_LAND,
|
|
101
|
+
grid_vars={"wet_mask": margin_mask},
|
|
102
|
+
)
|
|
103
|
+
hraw_extended = xr.concat([hraw, hraw.isel(eta_rho=-1)], dim="eta_rho")
|
|
104
|
+
hraw_extended = xr.concat(
|
|
105
|
+
[hraw_extended, hraw_extended.isel(xi_rho=-1)], dim="xi_rho"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
hsmooth = filter.apply(hraw_extended, dims=["eta_rho", "xi_rho"])
|
|
109
|
+
hsmooth = hsmooth.isel(eta_rho=slice(None, -1), xi_rho=slice(None, -1))
|
|
110
|
+
|
|
111
|
+
return hsmooth
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _fill_enclosed_basins(mask) -> np.ndarray:
|
|
115
|
+
"""
|
|
116
|
+
Fills in enclosed basins with land
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Label connected regions in the mask
|
|
120
|
+
reg, nreg = label(mask)
|
|
121
|
+
# Find the largest region
|
|
122
|
+
lint = 0
|
|
123
|
+
lreg = 0
|
|
124
|
+
for ireg in range(nreg):
|
|
125
|
+
int_ = np.sum(reg == ireg)
|
|
126
|
+
if int_ > lint and mask[reg == ireg].sum() > 0:
|
|
127
|
+
lreg = ireg
|
|
128
|
+
lint = int_
|
|
129
|
+
|
|
130
|
+
# Remove regions other than the largest one
|
|
131
|
+
for ireg in range(nreg):
|
|
132
|
+
if ireg != lreg:
|
|
133
|
+
mask[reg == ireg] = 0
|
|
134
|
+
|
|
135
|
+
return mask
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _smooth_topography_locally(h, hmin=5, rmax=0.2):
|
|
139
|
+
"""
|
|
140
|
+
Smoothes topography locally to satisfy r < rmax
|
|
141
|
+
"""
|
|
142
|
+
# Compute rmax_log
|
|
143
|
+
if rmax > 0.0:
|
|
144
|
+
rmax_log = np.log((1.0 + rmax * 0.9) / (1.0 - rmax * 0.9))
|
|
145
|
+
else:
|
|
146
|
+
rmax_log = 0.0
|
|
147
|
+
|
|
148
|
+
# Apply hmin threshold
|
|
149
|
+
h = xr.where(h < hmin, hmin, h)
|
|
150
|
+
|
|
151
|
+
# We will smooth logarithmically
|
|
152
|
+
h_log = np.log(h / hmin)
|
|
153
|
+
|
|
154
|
+
cf1 = 1.0 / 6
|
|
155
|
+
cf2 = 0.25
|
|
156
|
+
|
|
157
|
+
for iter in count():
|
|
158
|
+
# Compute gradients in domain interior
|
|
159
|
+
|
|
160
|
+
# in eta-direction
|
|
161
|
+
cff = h_log.diff("eta_rho").isel(xi_rho=slice(1, -1))
|
|
162
|
+
cr = np.abs(cff)
|
|
163
|
+
with warnings.catch_warnings():
|
|
164
|
+
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
165
|
+
Op1 = xr.where(cr < rmax_log, 0, 1.0 * cff * (1 - rmax_log / cr))
|
|
166
|
+
|
|
167
|
+
# in xi-direction
|
|
168
|
+
cff = h_log.diff("xi_rho").isel(eta_rho=slice(1, -1))
|
|
169
|
+
cr = np.abs(cff)
|
|
170
|
+
with warnings.catch_warnings():
|
|
171
|
+
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
172
|
+
Op2 = xr.where(cr < rmax_log, 0, 1.0 * cff * (1 - rmax_log / cr))
|
|
173
|
+
|
|
174
|
+
# in diagonal direction
|
|
175
|
+
cff = (h_log - h_log.shift(eta_rho=1, xi_rho=1)).isel(
|
|
176
|
+
eta_rho=slice(1, None), xi_rho=slice(1, None)
|
|
177
|
+
)
|
|
178
|
+
cr = np.abs(cff)
|
|
179
|
+
with warnings.catch_warnings():
|
|
180
|
+
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
181
|
+
Op3 = xr.where(cr < rmax_log, 0, 1.0 * cff * (1 - rmax_log / cr))
|
|
182
|
+
|
|
183
|
+
# in the other diagonal direction
|
|
184
|
+
cff = (h_log.shift(eta_rho=1) - h_log.shift(xi_rho=1)).isel(
|
|
185
|
+
eta_rho=slice(1, None), xi_rho=slice(1, None)
|
|
186
|
+
)
|
|
187
|
+
cr = np.abs(cff)
|
|
188
|
+
with warnings.catch_warnings():
|
|
189
|
+
warnings.simplefilter("ignore") # Ignore division by zero warning
|
|
190
|
+
Op4 = xr.where(cr < rmax_log, 0, 1.0 * cff * (1 - rmax_log / cr))
|
|
191
|
+
|
|
192
|
+
# Update h_log in domain interior
|
|
193
|
+
h_log[1:-1, 1:-1] += cf1 * (
|
|
194
|
+
Op1[1:, :]
|
|
195
|
+
- Op1[:-1, :]
|
|
196
|
+
+ Op2[:, 1:]
|
|
197
|
+
- Op2[:, :-1]
|
|
198
|
+
+ cf2 * (Op3[1:, 1:] - Op3[:-1, :-1] + Op4[:-1, 1:] - Op4[1:, :-1])
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# No gradient at the domain boundaries
|
|
202
|
+
h_log[0, :] = h_log[1, :]
|
|
203
|
+
h_log[-1, :] = h_log[-2, :]
|
|
204
|
+
h_log[:, 0] = h_log[:, 1]
|
|
205
|
+
h_log[:, -1] = h_log[:, -2]
|
|
206
|
+
|
|
207
|
+
# Update h
|
|
208
|
+
h = hmin * np.exp(h_log)
|
|
209
|
+
# Apply hmin threshold again
|
|
210
|
+
h = xr.where(h < hmin, hmin, h)
|
|
211
|
+
|
|
212
|
+
# compute maximum slope parameter r
|
|
213
|
+
r_eta, r_xi = _compute_rfactor(h)
|
|
214
|
+
rmax0 = np.max([r_eta.max(), r_xi.max()])
|
|
215
|
+
if rmax0 < rmax:
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
return h
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _compute_rfactor(h):
|
|
222
|
+
"""
|
|
223
|
+
Computes slope parameter (or r-factor) r = |Delta h| / 2h in both horizontal grid directions.
|
|
224
|
+
"""
|
|
225
|
+
# compute r_{i-1/2} = |h_i - h_{i-1}| / (h_i + h_{i+1})
|
|
226
|
+
r_eta = np.abs(h.diff("eta_rho")) / (h + h.shift(eta_rho=1)).isel(
|
|
227
|
+
eta_rho=slice(1, None)
|
|
228
|
+
)
|
|
229
|
+
r_xi = np.abs(h.diff("xi_rho")) / (h + h.shift(xi_rho=1)).isel(
|
|
230
|
+
xi_rho=slice(1, None)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return r_eta, r_xi
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _add_topography_metadata(ds, topography_source, smooth_factor, hmin, rmax):
|
|
237
|
+
ds.attrs["topography_source"] = topography_source
|
|
238
|
+
ds.attrs["smooth_factor"] = smooth_factor
|
|
239
|
+
ds.attrs["hmin"] = hmin
|
|
240
|
+
ds.attrs["rmax"] = rmax
|
|
241
|
+
|
|
242
|
+
return ds
|
roms_tools/tests/test_setup.py
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import numpy as np
|
|
3
3
|
import numpy.testing as npt
|
|
4
|
-
|
|
4
|
+
from scipy.ndimage import label
|
|
5
5
|
from roms_tools import Grid
|
|
6
|
+
from roms_tools.setup.topography import _compute_rfactor
|
|
7
|
+
from roms_tools.setup.tides import TPXO
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
class TestCreateGrid:
|
|
9
13
|
def test_simple_regression(self):
|
|
10
|
-
grid = Grid(
|
|
14
|
+
grid = Grid(
|
|
15
|
+
nx=1, ny=1, size_x=100, size_y=100, center_lon=-20, center_lat=0, rot=0
|
|
16
|
+
)
|
|
11
17
|
|
|
12
18
|
expected_lat = np.array(
|
|
13
19
|
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[-
|
|
20
|
+
[-8.99249453e-01, -8.99249453e-01, -8.99249453e-01],
|
|
21
|
+
[0.0, 0.0, 0.0],
|
|
22
|
+
[8.99249453e-01, 8.99249453e-01, 8.99249453e-01],
|
|
17
23
|
]
|
|
18
24
|
)
|
|
19
25
|
expected_lon = np.array(
|
|
@@ -24,18 +30,18 @@ class TestCreateGrid:
|
|
|
24
30
|
]
|
|
25
31
|
)
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
npt.assert_allclose(grid.ds["
|
|
33
|
+
# TODO: adapt tolerances according to order of magnitude of respective fields
|
|
34
|
+
npt.assert_allclose(grid.ds["lat_rho"], expected_lat, atol=1e-8)
|
|
35
|
+
npt.assert_allclose(grid.ds["lon_rho"], expected_lon, atol=1e-8)
|
|
29
36
|
|
|
30
|
-
def
|
|
31
|
-
with pytest.raises(ValueError, match="
|
|
32
|
-
|
|
33
|
-
Grid(nx=3, ny=3, size_x=100, size_y=100, center_lon=0, center_lat=51.5)
|
|
37
|
+
def test_raise_if_domain_too_large(self):
|
|
38
|
+
with pytest.raises(ValueError, match="Domain size has to be smaller"):
|
|
39
|
+
Grid(nx=3, ny=3, size_x=30000, size_y=30000, center_lon=0, center_lat=51.5)
|
|
34
40
|
|
|
35
|
-
# test
|
|
41
|
+
# test grid with reasonable domain size
|
|
36
42
|
grid = Grid(
|
|
37
|
-
nx=
|
|
38
|
-
ny=
|
|
43
|
+
nx=3,
|
|
44
|
+
ny=3,
|
|
39
45
|
size_x=1800,
|
|
40
46
|
size_y=2400,
|
|
41
47
|
center_lon=-21,
|
|
@@ -44,11 +50,132 @@ class TestCreateGrid:
|
|
|
44
50
|
)
|
|
45
51
|
assert isinstance(grid, Grid)
|
|
46
52
|
|
|
53
|
+
def test_grid_straddle_crosses_meridian(self):
|
|
54
|
+
grid = Grid(
|
|
55
|
+
nx=3,
|
|
56
|
+
ny=3,
|
|
57
|
+
size_x=100,
|
|
58
|
+
size_y=100,
|
|
59
|
+
center_lon=0,
|
|
60
|
+
center_lat=61,
|
|
61
|
+
rot=20,
|
|
62
|
+
)
|
|
63
|
+
assert grid.straddle
|
|
64
|
+
|
|
65
|
+
grid = Grid(
|
|
66
|
+
nx=3,
|
|
67
|
+
ny=3,
|
|
68
|
+
size_x=100,
|
|
69
|
+
size_y=100,
|
|
70
|
+
center_lon=180,
|
|
71
|
+
center_lat=61,
|
|
72
|
+
rot=20,
|
|
73
|
+
)
|
|
74
|
+
assert not grid.straddle
|
|
47
75
|
|
|
48
|
-
class TestGridFromFile:
|
|
49
|
-
def test_equal_to_from_init(self):
|
|
50
|
-
...
|
|
51
76
|
|
|
77
|
+
class TestGridFromFile:
|
|
52
78
|
def test_roundtrip(self):
|
|
53
79
|
"""Test that creating a grid, saving it to file, and re-opening it is the same as just creating it."""
|
|
54
|
-
|
|
80
|
+
|
|
81
|
+
# Initialize a Grid object using the initializer
|
|
82
|
+
grid_init = Grid(
|
|
83
|
+
nx=10,
|
|
84
|
+
ny=15,
|
|
85
|
+
size_x=100.0,
|
|
86
|
+
size_y=150.0,
|
|
87
|
+
center_lon=0.0,
|
|
88
|
+
center_lat=0.0,
|
|
89
|
+
rot=0.0,
|
|
90
|
+
topography_source="etopo5",
|
|
91
|
+
smooth_factor=2,
|
|
92
|
+
hmin=5.0,
|
|
93
|
+
rmax=0.2,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Create a temporary file
|
|
97
|
+
with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
|
|
98
|
+
filepath = tmpfile.name
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Save the grid to a file
|
|
102
|
+
grid_init.save(filepath)
|
|
103
|
+
|
|
104
|
+
# Load the grid from the file
|
|
105
|
+
grid_from_file = Grid.from_file(filepath)
|
|
106
|
+
|
|
107
|
+
# Assert that the initial grid and the loaded grid are equivalent (including the 'ds' attribute)
|
|
108
|
+
assert grid_init == grid_from_file
|
|
109
|
+
|
|
110
|
+
finally:
|
|
111
|
+
os.remove(filepath)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestTopography:
|
|
115
|
+
def test_enclosed_regions(self):
|
|
116
|
+
"""Test that there are only two connected regions, one dry and one wet."""
|
|
117
|
+
|
|
118
|
+
grid = Grid(
|
|
119
|
+
nx=100,
|
|
120
|
+
ny=100,
|
|
121
|
+
size_x=1800,
|
|
122
|
+
size_y=2400,
|
|
123
|
+
center_lon=30,
|
|
124
|
+
center_lat=61,
|
|
125
|
+
rot=20,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
reg, nreg = label(grid.ds.mask_rho)
|
|
129
|
+
npt.assert_equal(nreg, 2)
|
|
130
|
+
|
|
131
|
+
def test_rmax_criterion(self):
|
|
132
|
+
grid = Grid(
|
|
133
|
+
nx=100,
|
|
134
|
+
ny=100,
|
|
135
|
+
size_x=1800,
|
|
136
|
+
size_y=2400,
|
|
137
|
+
center_lon=30,
|
|
138
|
+
center_lat=61,
|
|
139
|
+
rot=20,
|
|
140
|
+
smooth_factor=4,
|
|
141
|
+
rmax=0.2,
|
|
142
|
+
)
|
|
143
|
+
r_eta, r_xi = _compute_rfactor(grid.ds.h)
|
|
144
|
+
rmax0 = np.max([r_eta.max(), r_xi.max()])
|
|
145
|
+
npt.assert_array_less(rmax0, grid.rmax)
|
|
146
|
+
|
|
147
|
+
def test_hmin_criterion(self):
|
|
148
|
+
grid = Grid(
|
|
149
|
+
nx=100,
|
|
150
|
+
ny=100,
|
|
151
|
+
size_x=1800,
|
|
152
|
+
size_y=2400,
|
|
153
|
+
center_lon=30,
|
|
154
|
+
center_lat=61,
|
|
155
|
+
rot=20,
|
|
156
|
+
smooth_factor=2,
|
|
157
|
+
rmax=0.2,
|
|
158
|
+
hmin=5,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TestTPXO:
|
|
165
|
+
def test_load_data_file_not_found(self):
|
|
166
|
+
# Test loading data from a non-existing file
|
|
167
|
+
with pytest.raises(FileNotFoundError):
|
|
168
|
+
TPXO.load_data("non_existing_file.nc")
|
|
169
|
+
|
|
170
|
+
def test_load_data_checksum_mismatch(self):
|
|
171
|
+
# Create a temporary file for testing
|
|
172
|
+
filename = "test_tidal_data.nc"
|
|
173
|
+
with open(filename, "wb") as file:
|
|
174
|
+
# Write some data to the file
|
|
175
|
+
file.write(b"test data")
|
|
176
|
+
# Test loading data with incorrect checksum
|
|
177
|
+
with open(filename, "wb") as file:
|
|
178
|
+
with pytest.raises(ValueError):
|
|
179
|
+
TPXO.load_data(filename)
|
|
180
|
+
# Remove temporary file
|
|
181
|
+
os.remove(filename)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: roms-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tools for running and analysing UCLA-ROMS simulations
|
|
5
|
+
Author-email: Nora Loose <nora.loose@gmail.com>, Thomas Nicholas <tom@cworthy.org>
|
|
6
|
+
License: Apache-2
|
|
7
|
+
Project-URL: Home, https://github.com/CWorthy-ocean/roms-tools
|
|
8
|
+
Project-URL: Documentation, https://roms-tools.readthedocs.io/en/latest/
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: xarray >=2022.6.0
|
|
22
|
+
Requires-Dist: numpy
|
|
23
|
+
Requires-Dist: netcdf4
|
|
24
|
+
Requires-Dist: pooch
|
|
25
|
+
Requires-Dist: matplotlib
|
|
26
|
+
Requires-Dist: cartopy
|
|
27
|
+
Requires-Dist: packaging
|
|
28
|
+
Requires-Dist: scipy
|
|
29
|
+
Requires-Dist: gcm-filters
|
|
30
|
+
Requires-Dist: numba
|
|
31
|
+
|
|
32
|
+
# ROMS-Tools
|
|
33
|
+
|
|
34
|
+
[](https://roms-tools.readthedocs.io/en/latest/?badge=latest)
|
|
35
|
+
[](https://badge.fury.io/py/roms-tools)
|
|
36
|
+
|
|
37
|
+
## Overview
|
|
38
|
+
|
|
39
|
+
A suite of python tools for setting up a [ROMS](https://github.com/CESR-lab/ucla-roms) simulation.
|
|
40
|
+
|
|
41
|
+
## Installation instructions
|
|
42
|
+
|
|
43
|
+
### Installation from pip
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install roms-tools
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Installation from GitHub
|
|
50
|
+
|
|
51
|
+
To obtain the latest development version, clone the source repository and install it:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/CWorthy-ocean/roms-tools.git
|
|
55
|
+
cd roms-tools
|
|
56
|
+
pip install -e .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Run the tests
|
|
61
|
+
|
|
62
|
+
Before running the tests you can install and activate the following conda environment:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd roms-tools
|
|
66
|
+
conda env create -f ci/environment.yml
|
|
67
|
+
conda activate romstools
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Check the installation of `ROMS-Tools` has worked by running the test suite
|
|
71
|
+
```bash
|
|
72
|
+
pytest
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Getting Started
|
|
76
|
+
|
|
77
|
+
To learn how to use `ROMS-Tools`, check out the [documentation](https://roms-tools.readthedocs.io/en/latest/).
|
|
78
|
+
|
|
79
|
+
## Feedback and contributions
|
|
80
|
+
|
|
81
|
+
**If you find a bug, have a feature suggestion, or any other kind of feedback, please start a Discussion.**
|
|
82
|
+
|
|
83
|
+
We also accept contributions in the form of Pull Requests.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
## See also
|
|
87
|
+
|
|
88
|
+
- [ROMS source code](https://github.com/CESR-lab/ucla-roms)
|
|
89
|
+
- [C-Star](https://github.com/CWorthy-ocean/C-Star)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
ci/environment.yml,sha256=OzTO5aTfzirFobAM6TmzJXZ622liCwIzreZH-x7n4mU,394
|
|
2
|
+
roms_tools/__init__.py,sha256=9MiZw2yYthIwOrktbOKwNptyZPmMV4_p4aCK4HymzUM,489
|
|
3
|
+
roms_tools/_version.py,sha256=FGsmFbZ942cZk42U_qHqxgRCbsD--5Vld_eR6xKJ-mQ,72
|
|
4
|
+
roms_tools/setup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
roms_tools/setup/atmospheric_forcing.py,sha256=A6wPmEfjeCIO3tu4TLt4Sul_J6AnF_peWZdON3I_6iw,34790
|
|
6
|
+
roms_tools/setup/datasets.py,sha256=0Hcsr7VzE-jo51kSMZ0ltHnIqc3fHe2v9lAC6ig2rH0,1943
|
|
7
|
+
roms_tools/setup/fill.py,sha256=8Klt77rkJqyXQ54TUv75KK_5iHcAwH-3bBtO3eoBieM,8701
|
|
8
|
+
roms_tools/setup/grid.py,sha256=p8AgCzUx5CkuxFcjFX6X65pYwEyOzxslx_nZDNJd-M4,26449
|
|
9
|
+
roms_tools/setup/plot.py,sha256=LZjTB4NHpXGsju_VZSCBwTfyox08nXHh6TkqmNBvz5Y,1809
|
|
10
|
+
roms_tools/setup/tides.py,sha256=aOez64Zv6MM1VFs1WIClKPpSg9vySvogqfqbg2eHKHA,20942
|
|
11
|
+
roms_tools/setup/topography.py,sha256=hj_IEbMTaazEtp6rrNbqAtGgPesWcCs8eHko0XHEhJg,8010
|
|
12
|
+
roms_tools/tests/test_setup.py,sha256=L90eM0IroRhYQ8ZY9BVOyUQND41lzdJKB_y2n7Aihmc,5102
|
|
13
|
+
roms_tools-0.1.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
14
|
+
roms_tools-0.1.0.dist-info/METADATA,sha256=XtN4Icf1h7Sh_ZbL9OFNCr9wIkvRpqTuo95szsLNtcg,2567
|
|
15
|
+
roms_tools-0.1.0.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
|
|
16
|
+
roms_tools-0.1.0.dist-info/top_level.txt,sha256=aAf4T4nYQSkay5iKJ9kmTjlDgd4ETdp9OSlB4sJdt8Y,19
|
|
17
|
+
roms_tools-0.1.0.dist-info/RECORD,,
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: roms-tools
|
|
3
|
-
Version: 0.0.6
|
|
4
|
-
Summary: Tools for running and analysing UCLA-ROMS simulations
|
|
5
|
-
Author-email: Thomas Nicholas <tom@cworthy.org>
|
|
6
|
-
License: Apache-2
|
|
7
|
-
Project-URL: Home, https://github.com/CWorthy-ocean/roms-tools
|
|
8
|
-
Project-URL: Documentation, https://github.com/CWorthy-ocean/roms-tools#readme
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: Topic :: Scientific/Engineering
|
|
12
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Requires-Python: >=3.9
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
License-File: LICENSE
|
|
22
|
-
Requires-Dist: xarray >=2022.6.0
|
|
23
|
-
Requires-Dist: numpy
|
|
24
|
-
Requires-Dist: netcdf4
|
|
25
|
-
Requires-Dist: matplotlib
|
|
26
|
-
Requires-Dist: cartopy
|
|
27
|
-
Requires-Dist: packaging
|
|
28
|
-
|
|
29
|
-
# ROMS-tools
|
|
30
|
-
|
|
31
|
-
## Overview
|
|
32
|
-
|
|
33
|
-
A suite of python tools for setting up a [ROMS](https://github.com/CESR-lab/ucla-roms) simulation.
|
|
34
|
-
|
|
35
|
-
_Note these tools are for the [Center for Earth Systems Research Group](http://research.atmos.ucla.edu/cesr/) at UCLA's version of ROMS._
|
|
36
|
-
|
|
37
|
-
## Installation instructions
|
|
38
|
-
|
|
39
|
-
Install via pip
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
pip install roms-tools
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Check the installation has worked by running the tests (you will need to also install pytest to run these)
|
|
46
|
-
```bash
|
|
47
|
-
pytest
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Dependencies required are xarray and netcdf4, plus matplotlib and cartopy for visualising grids.
|
|
51
|
-
|
|
52
|
-
ROMS-tools should run on any platform that can install the above dependencies.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## Usage instructions
|
|
56
|
-
|
|
57
|
-
To set up all the input files for a new ROMS simulation from scratch, follow these steps in order.
|
|
58
|
-
|
|
59
|
-
### Step 1: Make Grid
|
|
60
|
-
|
|
61
|
-
The first step is choosing the domain size, location, and resolution. Do this by creating an instance of the `Grid` class:
|
|
62
|
-
|
|
63
|
-
```python
|
|
64
|
-
from roms_tools import Grid
|
|
65
|
-
|
|
66
|
-
grid = Grid(
|
|
67
|
-
nx=100, # number of points in the x-direction (not including 2 boundary cells on either end)
|
|
68
|
-
ny=100, # number of points in the y-direction (not including 2 boundary cells on either end)
|
|
69
|
-
size_x=1800, # size of the domain in the x-direction (in km)
|
|
70
|
-
size_y=2400, # size of the domain in the y-direction (in km)
|
|
71
|
-
center_lon=-21, # longitude of the center of the domain
|
|
72
|
-
center_lat=61, # latitude of the center of the domain
|
|
73
|
-
rot=20, # rotation of the grid's x-direction from lines of constant longitude, with positive values being a counter-clockwise rotation
|
|
74
|
-
)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
To visualize the grid we have just created, use the `.plot` method:
|
|
78
|
-
|
|
79
|
-
```python
|
|
80
|
-
grid.plot()
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-

|
|
84
|
-
|
|
85
|
-
To see the values of the grid variables you can examine the `xarray.Dataset` object returned by the `.ds` property
|
|
86
|
-
|
|
87
|
-
```python
|
|
88
|
-
grid.ds
|
|
89
|
-
```
|
|
90
|
-
```
|
|
91
|
-
<xarray.Dataset>
|
|
92
|
-
Dimensions: (eta_rho: 3, xi_rho: 3, one: 1)
|
|
93
|
-
Dimensions without coordinates: eta_rho, xi_rho, one
|
|
94
|
-
Data variables:
|
|
95
|
-
angle (eta_rho, xi_rho) float64 0.0 0.0 0.0 -1.46e-16 ... 0.0 0.0 0.0
|
|
96
|
-
f0 (eta_rho, xi_rho) float64 4.565e-06 4.565e-06 ... -4.565e-06
|
|
97
|
-
pn (eta_rho, xi_rho) float64 5e-06 5e-06 5e-06 ... 5e-06 5e-06 5e-06
|
|
98
|
-
lon_rho (eta_rho, xi_rho) float64 339.1 340.0 340.9 ... 339.1 340.0 340.9
|
|
99
|
-
lat_rho (eta_rho, xi_rho) float64 1.799 1.799 1.799 ... -1.799 -1.799
|
|
100
|
-
spherical (one) <U1 'T'
|
|
101
|
-
tra_lon (one) int64 -20
|
|
102
|
-
tra_lat (one) int64 0
|
|
103
|
-
rotate (one) int64 0
|
|
104
|
-
Attributes:
|
|
105
|
-
Title: ROMS grid. Settings: nx: 1 ny: 1 xsize: 0.1 ysize: 0.1 rotate:...
|
|
106
|
-
Date: 2023-11-20
|
|
107
|
-
Type: ROMS grid produced by roms-tools
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Once we are happy with our grid, we can save it as a netCDF file via the `.save` method:
|
|
111
|
-
|
|
112
|
-
```python
|
|
113
|
-
grid.save('grids/my_new_roms_grid.nc')
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
The basic grid domain is now ready for use by ROMS.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
### Steps 2-7:
|
|
120
|
-
|
|
121
|
-
Coming soon!
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
## Feedback and contributions
|
|
125
|
-
|
|
126
|
-
**If you find a bug, have a feature suggestion, or any other kind of feedback, please start a Discussion.**
|
|
127
|
-
|
|
128
|
-
We also accept contributions in the form of Pull Requests.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
## See also
|
|
132
|
-
|
|
133
|
-
- [ROMS source code](https://github.com/CESR-lab/ucla-roms)
|
|
134
|
-
- [C-Star](https://github.com/CWorthy-ocean/C-Star)
|