roms-tools 0.0.2__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.2.dist-info → roms_tools-0.1.0.dist-info}/WHEEL +1 -1
- {roms_tools-0.0.2.dist-info → roms_tools-0.1.0.dist-info}/top_level.txt +2 -0
- roms_tools/setup/old_grid_script.py +0 -438
- roms_tools-0.0.2.dist-info/METADATA +0 -134
- roms_tools-0.0.2.dist-info/RECORD +0 -11
- {roms_tools-0.0.2.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,,
|