roms-tools 0.0.6__py3-none-any.whl → 0.20__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.
@@ -0,0 +1,226 @@
1
+ import pytest
2
+ import numpy as np
3
+ import numpy.testing as npt
4
+ from roms_tools import Grid
5
+ import os
6
+ import tempfile
7
+ import importlib.metadata
8
+ import textwrap
9
+
10
+
11
+ def test_simple_regression():
12
+ grid = Grid(nx=1, ny=1, size_x=100, size_y=100, center_lon=-20, center_lat=0, rot=0)
13
+
14
+ expected_lat = np.array(
15
+ [
16
+ [-8.99249453e-01, -8.99249453e-01, -8.99249453e-01],
17
+ [0.0, 0.0, 0.0],
18
+ [8.99249453e-01, 8.99249453e-01, 8.99249453e-01],
19
+ ]
20
+ )
21
+ expected_lon = np.array(
22
+ [
23
+ [339.10072286, 340.0, 340.89927714],
24
+ [339.10072286, 340.0, 340.89927714],
25
+ [339.10072286, 340.0, 340.89927714],
26
+ ]
27
+ )
28
+
29
+ # TODO: adapt tolerances according to order of magnitude of respective fields
30
+ npt.assert_allclose(grid.ds["lat_rho"], expected_lat, atol=1e-8)
31
+ npt.assert_allclose(grid.ds["lon_rho"], expected_lon, atol=1e-8)
32
+
33
+
34
+ def test_raise_if_domain_too_large():
35
+ with pytest.raises(ValueError, match="Domain size has to be smaller"):
36
+ Grid(nx=3, ny=3, size_x=30000, size_y=30000, center_lon=0, center_lat=51.5)
37
+
38
+ # test grid with reasonable domain size
39
+ grid = Grid(
40
+ nx=3,
41
+ ny=3,
42
+ size_x=1800,
43
+ size_y=2400,
44
+ center_lon=-21,
45
+ center_lat=61,
46
+ rot=20,
47
+ )
48
+ assert isinstance(grid, Grid)
49
+
50
+
51
+ def test_grid_straddle_crosses_meridian():
52
+ grid = Grid(
53
+ nx=3,
54
+ ny=3,
55
+ size_x=100,
56
+ size_y=100,
57
+ center_lon=0,
58
+ center_lat=61,
59
+ rot=20,
60
+ )
61
+ assert grid.straddle
62
+
63
+ grid = Grid(
64
+ nx=3,
65
+ ny=3,
66
+ size_x=100,
67
+ size_y=100,
68
+ center_lon=180,
69
+ center_lat=61,
70
+ rot=20,
71
+ )
72
+ assert not grid.straddle
73
+
74
+
75
+ def test_roundtrip_netcdf():
76
+ """Test that creating a grid, saving it to file, and re-opening it is the same as just creating it."""
77
+
78
+ # Initialize a Grid object using the initializer
79
+ grid_init = Grid(
80
+ nx=10,
81
+ ny=15,
82
+ size_x=100.0,
83
+ size_y=150.0,
84
+ center_lon=0.0,
85
+ center_lat=0.0,
86
+ rot=0.0,
87
+ topography_source="ETOPO5",
88
+ smooth_factor=2,
89
+ hmin=5.0,
90
+ rmax=0.2,
91
+ )
92
+
93
+ # Create a temporary file
94
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
95
+ filepath = tmpfile.name
96
+
97
+ try:
98
+ # Save the grid to a file
99
+ grid_init.save(filepath)
100
+
101
+ # Load the grid from the file
102
+ grid_from_file = Grid.from_file(filepath)
103
+
104
+ # Assert that the initial grid and the loaded grid are equivalent (including the 'ds' attribute)
105
+ assert grid_init == grid_from_file
106
+
107
+ finally:
108
+ os.remove(filepath)
109
+
110
+
111
+ def test_roundtrip_yaml():
112
+ """Test that creating a grid, saving its parameters to yaml file, and re-opening yaml file creates the same grid."""
113
+
114
+ # Initialize a Grid object using the initializer
115
+ grid_init = Grid(
116
+ nx=10,
117
+ ny=15,
118
+ size_x=100.0,
119
+ size_y=150.0,
120
+ center_lon=0.0,
121
+ center_lat=0.0,
122
+ rot=0.0,
123
+ topography_source="ETOPO5",
124
+ smooth_factor=2,
125
+ hmin=5.0,
126
+ rmax=0.2,
127
+ )
128
+
129
+ # Create a temporary file
130
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
131
+ filepath = tmpfile.name
132
+
133
+ try:
134
+ grid_init.to_yaml(filepath)
135
+
136
+ grid_from_file = Grid.from_yaml(filepath)
137
+
138
+ # Assert that the initial grid and the loaded grid are equivalent (including the 'ds' attribute)
139
+ assert grid_init == grid_from_file
140
+
141
+ finally:
142
+ os.remove(filepath)
143
+
144
+
145
+ def test_from_yaml_missing_version():
146
+
147
+ yaml_content = textwrap.dedent(
148
+ """\
149
+ Grid:
150
+ nx: 100
151
+ ny: 100
152
+ size_x: 1800
153
+ size_y: 2400
154
+ center_lon: -10
155
+ center_lat: 61
156
+ rot: -20
157
+ topography_source: ETOPO5
158
+ smooth_factor: 8
159
+ hmin: 5.0
160
+ rmax: 0.2
161
+ """
162
+ )
163
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
164
+ yaml_filepath = tmp_file.name
165
+ tmp_file.write(yaml_content.encode())
166
+
167
+ try:
168
+ with pytest.raises(
169
+ ValueError, match="Version of ROMS-Tools not found in the YAML file."
170
+ ):
171
+ Grid.from_yaml(yaml_filepath)
172
+ finally:
173
+ os.remove(yaml_filepath)
174
+
175
+
176
+ def test_from_yaml_missing_grid():
177
+ roms_tools_version = importlib.metadata.version("roms-tools")
178
+
179
+ yaml_content = f"---\nroms_tools_version: {roms_tools_version}\n---\n"
180
+
181
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
182
+ yaml_filepath = tmp_file.name
183
+ tmp_file.write(yaml_content.encode())
184
+
185
+ try:
186
+ with pytest.raises(
187
+ ValueError, match="No Grid configuration found in the YAML file."
188
+ ):
189
+ Grid.from_yaml(yaml_filepath)
190
+ finally:
191
+ os.remove(yaml_filepath)
192
+
193
+
194
+ def test_from_yaml_version_mismatch():
195
+ yaml_content = textwrap.dedent(
196
+ """\
197
+ ---
198
+ roms_tools_version: 0.0.0
199
+ ---
200
+ Grid:
201
+ nx: 100
202
+ ny: 100
203
+ size_x: 1800
204
+ size_y: 2400
205
+ center_lon: -10
206
+ center_lat: 61
207
+ rot: -20
208
+ topography_source: ETOPO5
209
+ smooth_factor: 8
210
+ hmin: 5.0
211
+ rmax: 0.2
212
+ """
213
+ )
214
+
215
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
216
+ yaml_filepath = tmp_file.name
217
+ tmp_file.write(yaml_content.encode())
218
+
219
+ try:
220
+ with pytest.warns(
221
+ UserWarning,
222
+ match="Current roms-tools version.*does not match the version in the YAML header.*",
223
+ ):
224
+ Grid.from_yaml(yaml_filepath)
225
+ finally:
226
+ os.remove(yaml_filepath)
@@ -0,0 +1,300 @@
1
+ import pytest
2
+ from datetime import datetime
3
+ from roms_tools import InitialConditions, Grid, VerticalCoordinate
4
+ import xarray as xr
5
+ import numpy as np
6
+ import tempfile
7
+ import os
8
+ import textwrap
9
+ from roms_tools.setup.datasets import download_test_data
10
+
11
+
12
+ @pytest.fixture
13
+ def example_grid():
14
+ """
15
+ Fixture for creating a Grid object.
16
+ """
17
+ grid = Grid(
18
+ nx=2, ny=2, size_x=500, size_y=1000, center_lon=0, center_lat=55, rot=10
19
+ )
20
+
21
+ return grid
22
+
23
+
24
+ @pytest.fixture
25
+ def example_vertical_coordinate(example_grid):
26
+ """
27
+ Fixture for creating a VerticalCoordinate object.
28
+ """
29
+ vertical_coordinate = VerticalCoordinate(
30
+ grid=example_grid,
31
+ N=3, # number of vertical levels
32
+ theta_s=5.0, # surface control parameter
33
+ theta_b=2.0, # bottom control parameter
34
+ hc=250.0, # critical depth
35
+ )
36
+
37
+ return vertical_coordinate
38
+
39
+
40
+ @pytest.fixture
41
+ def initial_conditions(example_grid, example_vertical_coordinate):
42
+ """
43
+ Fixture for creating a dummy InitialConditions object.
44
+ """
45
+
46
+ fname = download_test_data("GLORYS_test_data.nc")
47
+
48
+ return InitialConditions(
49
+ grid=example_grid,
50
+ vertical_coordinate=example_vertical_coordinate,
51
+ ini_time=datetime(2021, 6, 29),
52
+ filename=fname,
53
+ )
54
+
55
+
56
+ def test_initial_conditions_creation(initial_conditions):
57
+ """
58
+ Test the creation of the InitialConditions object.
59
+ """
60
+ assert initial_conditions.ini_time == datetime(2021, 6, 29)
61
+ assert initial_conditions.filename == download_test_data("GLORYS_test_data.nc")
62
+ assert initial_conditions.source == "GLORYS"
63
+
64
+
65
+ def test_initial_conditions_ds_attribute(initial_conditions):
66
+ """
67
+ Test the ds attribute of the InitialConditions object.
68
+ """
69
+ assert isinstance(initial_conditions.ds, xr.Dataset)
70
+ assert "temp" in initial_conditions.ds
71
+ assert "salt" in initial_conditions.ds
72
+ assert "u" in initial_conditions.ds
73
+ assert "v" in initial_conditions.ds
74
+ assert "zeta" in initial_conditions.ds
75
+
76
+
77
+ def test_initial_conditions_data_consistency_plot_save(initial_conditions, tmp_path):
78
+ """
79
+ Test that the data within the InitialConditions object remains consistent.
80
+ Also test plot and save methods in the same test since we dask arrays are already computed.
81
+ """
82
+ initial_conditions.ds.load()
83
+
84
+ # Define the expected data
85
+ expected_temp = np.array(
86
+ [
87
+ [
88
+ [
89
+ [16.84414, 16.905312, 16.967817],
90
+ [18.088203, 18.121834, 18.315424],
91
+ [18.431192, 18.496748, 18.718002],
92
+ [19.294329, 19.30358, 19.439777],
93
+ ],
94
+ [
95
+ [12.639833, 13.479691, 14.426711],
96
+ [15.712767, 15.920951, 16.10028],
97
+ [13.02848, 14.5227165, 15.05175],
98
+ [18.633307, 18.637077, 18.667465],
99
+ ],
100
+ [
101
+ [11.027701, 11.650267, 12.200586],
102
+ [12.302642, 12.646921, 13.150708],
103
+ [8.143677, 11.435992, 13.356925],
104
+ [8.710737, 11.25943, 13.111585],
105
+ ],
106
+ [
107
+ [10.233599, 10.546486, 10.671082],
108
+ [10.147331, 10.502733, 10.68275],
109
+ [10.458557, 11.209945, 11.377164],
110
+ [9.20282, 10.667074, 11.752404],
111
+ ],
112
+ ]
113
+ ],
114
+ dtype=np.float32,
115
+ )
116
+ expected_salt = np.array(
117
+ [
118
+ [
119
+ [
120
+ [33.832672, 33.77759, 33.633846],
121
+ [32.50002, 32.48105, 32.154694],
122
+ [30.922323, 30.909824, 30.508572],
123
+ [28.337738, 28.335176, 28.067144],
124
+ ],
125
+ [
126
+ [34.8002, 34.691143, 34.382282],
127
+ [32.43797, 32.369576, 32.027843],
128
+ [34.885834, 34.82964, 34.775684],
129
+ [29.237692, 29.232145, 29.09444],
130
+ ],
131
+ [
132
+ [34.046825, 33.950684, 33.87148],
133
+ [33.892323, 33.84102, 33.69169],
134
+ [34.964134, 34.91892, 34.91941],
135
+ [34.975933, 34.48586, 32.729057],
136
+ ],
137
+ [
138
+ [35.21593, 35.209476, 35.20767],
139
+ [35.224304, 35.209522, 35.20715],
140
+ [35.299217, 35.31244, 35.31555],
141
+ [34.25124, 33.828175, 33.234303],
142
+ ],
143
+ ]
144
+ ],
145
+ dtype=np.float32,
146
+ )
147
+
148
+ expected_zeta = np.array(
149
+ [
150
+ [
151
+ [-0.30468762, -0.29416865, -0.30391693, -0.32985148],
152
+ [-0.34336275, -0.29455253, -0.3718359, -0.36176518],
153
+ [-0.3699948, -0.34693155, -0.41338325, -0.40663475],
154
+ [-0.5534979, -0.5270749, -0.45107934, -0.40699923],
155
+ ]
156
+ ],
157
+ dtype=np.float32,
158
+ )
159
+
160
+ expected_u = np.array(
161
+ [
162
+ [
163
+ [[-0.0, -0.0, -0.0], [-0.0, -0.0, -0.0], [0.0, -0.0, -0.0]],
164
+ [[0.0, -0.0, -0.0], [-0.0, -0.0, -0.0], [-0.0, -0.0, -0.0]],
165
+ [
166
+ [0.0, 0.0, -0.0],
167
+ [0.0, 0.0, -0.0],
168
+ [0.06979556, 0.06167743, -0.02247071],
169
+ ],
170
+ [
171
+ [0.04268532, 0.03889201, 0.03351666],
172
+ [0.04645353, 0.04914769, 0.03673013],
173
+ [0.0211786, 0.03679834, 0.0274788],
174
+ ],
175
+ ]
176
+ ],
177
+ dtype=np.float32,
178
+ )
179
+
180
+ expected_v = np.array(
181
+ [
182
+ [
183
+ [
184
+ [0.0, 0.0, 0.0],
185
+ [0.0, 0.0, -0.0],
186
+ [-0.0, -0.0, -0.0],
187
+ [-0.0, -0.0, -0.0],
188
+ ],
189
+ [
190
+ [-0.0, -0.0, -0.0],
191
+ [-0.0, -0.0, -0.0],
192
+ [-0.03831354, -0.02400788, -0.03179555],
193
+ [-0.0, -0.0, -0.0],
194
+ ],
195
+ [
196
+ [-0.00951457, -0.00576979, -0.02147919],
197
+ [-0.0, -0.0, -0.0],
198
+ [0.01915873, 0.02625698, 0.01757628],
199
+ [-0.06720348, -0.08354441, -0.13835917],
200
+ ],
201
+ ]
202
+ ],
203
+ dtype=np.float32,
204
+ )
205
+
206
+ expected_ubar = np.array(
207
+ [
208
+ [
209
+ [0.0, 0.0, 0.0],
210
+ [0.0, 0.0, 0.0],
211
+ [0.0, 0.0, 0.04028399],
212
+ [0.03866891, 0.04446249, 0.02812303],
213
+ ]
214
+ ],
215
+ dtype=np.float32,
216
+ )
217
+
218
+ expected_vbar = np.array(
219
+ [
220
+ [
221
+ [0.0, 0.0, 0.0, 0.0],
222
+ [0.0, 0.0, -0.03169237, 0.0],
223
+ [-0.01189703, 0.0, 0.02102064, -0.09326097],
224
+ ]
225
+ ],
226
+ dtype=np.float32,
227
+ )
228
+
229
+ # Check the values in the dataset
230
+ assert np.allclose(initial_conditions.ds["temp"].values, expected_temp)
231
+ assert np.allclose(initial_conditions.ds["salt"].values, expected_salt)
232
+ assert np.allclose(initial_conditions.ds["zeta"].values, expected_zeta)
233
+ assert np.allclose(initial_conditions.ds["u"].values, expected_u)
234
+ assert np.allclose(initial_conditions.ds["v"].values, expected_v)
235
+ assert np.allclose(initial_conditions.ds["ubar"].values, expected_ubar)
236
+ assert np.allclose(initial_conditions.ds["vbar"].values, expected_vbar)
237
+
238
+ initial_conditions.plot(varname="temp", s=0)
239
+ initial_conditions.plot(varname="temp", eta=0)
240
+ initial_conditions.plot(varname="temp", xi=0)
241
+ initial_conditions.plot(varname="temp", s=0, xi=0)
242
+ initial_conditions.plot(varname="temp", eta=0, xi=0)
243
+ initial_conditions.plot(varname="zeta")
244
+
245
+ filepath = tmp_path / "initial_conditions.nc"
246
+ initial_conditions.save(filepath)
247
+ assert filepath.exists()
248
+
249
+
250
+ def test_roundtrip_yaml(initial_conditions):
251
+ """Test that creating an InitialConditions object, saving its parameters to yaml file, and re-opening yaml file creates the same object."""
252
+
253
+ # Create a temporary file
254
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
255
+ filepath = tmpfile.name
256
+
257
+ try:
258
+ initial_conditions.to_yaml(filepath)
259
+
260
+ initial_conditions_from_file = InitialConditions.from_yaml(filepath)
261
+
262
+ assert initial_conditions == initial_conditions_from_file
263
+
264
+ finally:
265
+ os.remove(filepath)
266
+
267
+
268
+ def test_from_yaml_missing_initial_conditions():
269
+ yaml_content = textwrap.dedent(
270
+ """\
271
+ ---
272
+ roms_tools_version: 0.0.0
273
+ ---
274
+ Grid:
275
+ nx: 100
276
+ ny: 100
277
+ size_x: 1800
278
+ size_y: 2400
279
+ center_lon: -10
280
+ center_lat: 61
281
+ rot: -20
282
+ topography_source: ETOPO5
283
+ smooth_factor: 8
284
+ hmin: 5.0
285
+ rmax: 0.2
286
+ """
287
+ )
288
+
289
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
290
+ yaml_filepath = tmp_file.name
291
+ tmp_file.write(yaml_content.encode())
292
+
293
+ try:
294
+ with pytest.raises(
295
+ ValueError,
296
+ match="No InitialConditions configuration found in the YAML file.",
297
+ ):
298
+ InitialConditions.from_yaml(yaml_filepath)
299
+ finally:
300
+ os.remove(yaml_filepath)