roms-tools 0.1.0__py3-none-any.whl → 1.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.
@@ -0,0 +1,520 @@
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.download import download_test_data
10
+ from roms_tools.setup.datasets import CESMBGCDataset
11
+
12
+
13
+ @pytest.fixture
14
+ def example_grid():
15
+ """
16
+ Fixture for creating a Grid object.
17
+ """
18
+ grid = Grid(
19
+ nx=2, ny=2, size_x=500, size_y=1000, center_lon=0, center_lat=55, rot=10
20
+ )
21
+
22
+ return grid
23
+
24
+
25
+ @pytest.fixture
26
+ def example_vertical_coordinate(example_grid):
27
+ """
28
+ Fixture for creating a VerticalCoordinate object.
29
+ """
30
+ vertical_coordinate = VerticalCoordinate(
31
+ grid=example_grid,
32
+ N=3, # number of vertical levels
33
+ theta_s=5.0, # surface control parameter
34
+ theta_b=2.0, # bottom control parameter
35
+ hc=250.0, # critical depth
36
+ )
37
+
38
+ return vertical_coordinate
39
+
40
+
41
+ @pytest.fixture
42
+ def initial_conditions(example_grid, example_vertical_coordinate):
43
+ """
44
+ Fixture for creating a dummy InitialConditions object.
45
+ """
46
+
47
+ fname = download_test_data("GLORYS_test_data.nc")
48
+
49
+ return InitialConditions(
50
+ grid=example_grid,
51
+ vertical_coordinate=example_vertical_coordinate,
52
+ ini_time=datetime(2021, 6, 29),
53
+ physics_source={"path": fname, "name": "GLORYS"},
54
+ )
55
+
56
+
57
+ @pytest.fixture
58
+ def initial_conditions_with_bgc(example_grid, example_vertical_coordinate):
59
+ """
60
+ Fixture for creating a dummy InitialConditions object.
61
+ """
62
+
63
+ fname = download_test_data("GLORYS_test_data.nc")
64
+ fname_bgc = download_test_data("CESM_regional_test_data_one_time_slice.nc")
65
+
66
+ return InitialConditions(
67
+ grid=example_grid,
68
+ vertical_coordinate=example_vertical_coordinate,
69
+ ini_time=datetime(2021, 6, 29),
70
+ physics_source={"path": fname, "name": "GLORYS"},
71
+ bgc_source={"path": fname_bgc, "name": "CESM_REGRIDDED"},
72
+ )
73
+
74
+
75
+ @pytest.fixture
76
+ def initial_conditions_with_bgc_from_climatology(
77
+ example_grid, example_vertical_coordinate
78
+ ):
79
+ """
80
+ Fixture for creating a dummy InitialConditions object.
81
+ """
82
+
83
+ fname = download_test_data("GLORYS_test_data.nc")
84
+ fname_bgc = download_test_data("CESM_regional_test_data_climatology.nc")
85
+
86
+ return InitialConditions(
87
+ grid=example_grid,
88
+ vertical_coordinate=example_vertical_coordinate,
89
+ ini_time=datetime(2021, 6, 29),
90
+ physics_source={"path": fname, "name": "GLORYS"},
91
+ bgc_source={
92
+ "path": fname_bgc,
93
+ "name": "CESM_REGRIDDED",
94
+ "climatology": True,
95
+ },
96
+ )
97
+
98
+
99
+ @pytest.mark.parametrize(
100
+ "ic_fixture",
101
+ [
102
+ "initial_conditions",
103
+ "initial_conditions_with_bgc",
104
+ "initial_conditions_with_bgc_from_climatology",
105
+ ],
106
+ )
107
+ def test_initial_conditions_creation(ic_fixture, request):
108
+ """
109
+ Test the creation of the InitialConditions object.
110
+ """
111
+
112
+ ic = request.getfixturevalue(ic_fixture)
113
+
114
+ assert ic.ini_time == datetime(2021, 6, 29)
115
+ assert ic.physics_source == {
116
+ "name": "GLORYS",
117
+ "path": download_test_data("GLORYS_test_data.nc"),
118
+ "climatology": False,
119
+ }
120
+ assert isinstance(ic.ds, xr.Dataset)
121
+ assert "temp" in ic.ds
122
+ assert "salt" in ic.ds
123
+ assert "u" in ic.ds
124
+ assert "v" in ic.ds
125
+ assert "zeta" in ic.ds
126
+
127
+
128
+ # Test initialization with missing 'name' in physics_source
129
+ def test_initial_conditions_missing_physics_name(
130
+ example_grid, example_vertical_coordinate
131
+ ):
132
+ with pytest.raises(ValueError, match="`physics_source` must include a 'name'."):
133
+ InitialConditions(
134
+ grid=example_grid,
135
+ vertical_coordinate=example_vertical_coordinate,
136
+ ini_time=datetime(2021, 6, 29),
137
+ physics_source={"path": "physics_data.nc"},
138
+ )
139
+
140
+
141
+ # Test initialization with missing 'path' in physics_source
142
+ def test_initial_conditions_missing_physics_path(
143
+ example_grid, example_vertical_coordinate
144
+ ):
145
+ with pytest.raises(ValueError, match="`physics_source` must include a 'path'."):
146
+ InitialConditions(
147
+ grid=example_grid,
148
+ vertical_coordinate=example_vertical_coordinate,
149
+ ini_time=datetime(2021, 6, 29),
150
+ physics_source={"name": "GLORYS"},
151
+ )
152
+
153
+
154
+ # Test initialization with missing 'name' in bgc_source
155
+ def test_initial_conditions_missing_bgc_name(example_grid, example_vertical_coordinate):
156
+
157
+ fname = download_test_data("GLORYS_test_data.nc")
158
+ with pytest.raises(
159
+ ValueError, match="`bgc_source` must include a 'name' if it is provided."
160
+ ):
161
+ InitialConditions(
162
+ grid=example_grid,
163
+ vertical_coordinate=example_vertical_coordinate,
164
+ ini_time=datetime(2021, 6, 29),
165
+ physics_source={"name": "GLORYS", "path": fname},
166
+ bgc_source={"path": "bgc_data.nc"},
167
+ )
168
+
169
+
170
+ # Test initialization with missing 'path' in bgc_source
171
+ def test_initial_conditions_missing_bgc_path(example_grid, example_vertical_coordinate):
172
+
173
+ fname = download_test_data("GLORYS_test_data.nc")
174
+ with pytest.raises(
175
+ ValueError, match="`bgc_source` must include a 'path' if it is provided."
176
+ ):
177
+ InitialConditions(
178
+ grid=example_grid,
179
+ vertical_coordinate=example_vertical_coordinate,
180
+ ini_time=datetime(2021, 6, 29),
181
+ physics_source={"name": "GLORYS", "path": fname},
182
+ bgc_source={"name": "CESM_REGRIDDED"},
183
+ )
184
+
185
+
186
+ # Test default climatology value
187
+ def test_initial_conditions_default_climatology(
188
+ example_grid, example_vertical_coordinate
189
+ ):
190
+
191
+ fname = download_test_data("GLORYS_test_data.nc")
192
+
193
+ initial_conditions = InitialConditions(
194
+ grid=example_grid,
195
+ vertical_coordinate=example_vertical_coordinate,
196
+ ini_time=datetime(2021, 6, 29),
197
+ physics_source={"name": "GLORYS", "path": fname},
198
+ )
199
+
200
+ assert initial_conditions.physics_source["climatology"] is False
201
+ assert initial_conditions.bgc_source is None
202
+
203
+
204
+ def test_initial_conditions_default_bgc_climatology(
205
+ example_grid, example_vertical_coordinate
206
+ ):
207
+
208
+ fname = download_test_data("GLORYS_test_data.nc")
209
+ fname_bgc = download_test_data("CESM_regional_test_data_one_time_slice.nc")
210
+
211
+ initial_conditions = InitialConditions(
212
+ grid=example_grid,
213
+ vertical_coordinate=example_vertical_coordinate,
214
+ ini_time=datetime(2021, 6, 29),
215
+ physics_source={"name": "GLORYS", "path": fname},
216
+ bgc_source={"name": "CESM_REGRIDDED", "path": fname_bgc},
217
+ )
218
+
219
+ assert initial_conditions.bgc_source["climatology"] is True
220
+
221
+
222
+ def test_interpolation_from_climatology(initial_conditions_with_bgc_from_climatology):
223
+
224
+ fname_bgc = download_test_data("CESM_regional_test_data_climatology.nc")
225
+ ds = xr.open_dataset(fname_bgc)
226
+
227
+ # check if interpolated value for Jan 15 is indeed January value from climatology
228
+ bgc_data = CESMBGCDataset(
229
+ filename=fname_bgc, start_time=datetime(2012, 1, 15), climatology=True
230
+ )
231
+ assert np.allclose(ds["ALK"].sel(month=1), bgc_data.ds["ALK"], equal_nan=True)
232
+
233
+ # check if interpolated value for Jan 30 is indeed average of January and February value from climatology
234
+ bgc_data = CESMBGCDataset(
235
+ filename=fname_bgc, start_time=datetime(2012, 1, 30), climatology=True
236
+ )
237
+ assert np.allclose(
238
+ 0.5 * (ds["ALK"].sel(month=1) + ds["ALK"].sel(month=2)),
239
+ bgc_data.ds["ALK"],
240
+ equal_nan=True,
241
+ )
242
+
243
+
244
+ def test_initial_conditions_data_consistency_plot_save(
245
+ initial_conditions_with_bgc_from_climatology, tmp_path
246
+ ):
247
+ """
248
+ Test that the data within the InitialConditions object remains consistent.
249
+ Also test plot and save methods in the same test since we dask arrays are already computed.
250
+ """
251
+ initial_conditions_with_bgc_from_climatology.ds.load()
252
+
253
+ # Define the expected data
254
+ expected_temp = np.array(
255
+ [
256
+ [
257
+ [
258
+ [16.84414, 16.905312, 16.967817],
259
+ [18.088203, 18.121834, 18.315424],
260
+ [18.431192, 18.496748, 18.718002],
261
+ [19.294329, 19.30358, 19.439777],
262
+ ],
263
+ [
264
+ [12.639833, 13.479691, 14.426711],
265
+ [15.712767, 15.920951, 16.10028],
266
+ [13.02848, 14.5227165, 15.05175],
267
+ [18.633307, 18.637077, 18.667465],
268
+ ],
269
+ [
270
+ [11.027701, 11.650267, 12.200586],
271
+ [12.302642, 12.646921, 13.150708],
272
+ [8.143677, 11.435992, 13.356925],
273
+ [8.710737, 11.25943, 13.111585],
274
+ ],
275
+ [
276
+ [10.233599, 10.546486, 10.671082],
277
+ [10.147331, 10.502733, 10.68275],
278
+ [10.458557, 11.209945, 11.377164],
279
+ [9.20282, 10.667074, 11.752404],
280
+ ],
281
+ ]
282
+ ],
283
+ dtype=np.float32,
284
+ )
285
+ expected_salt = np.array(
286
+ [
287
+ [
288
+ [
289
+ [33.832672, 33.77759, 33.633846],
290
+ [32.50002, 32.48105, 32.154694],
291
+ [30.922323, 30.909824, 30.508572],
292
+ [28.337738, 28.335176, 28.067144],
293
+ ],
294
+ [
295
+ [34.8002, 34.691143, 34.382282],
296
+ [32.43797, 32.369576, 32.027843],
297
+ [34.885834, 34.82964, 34.775684],
298
+ [29.237692, 29.232145, 29.09444],
299
+ ],
300
+ [
301
+ [34.046825, 33.950684, 33.87148],
302
+ [33.892323, 33.84102, 33.69169],
303
+ [34.964134, 34.91892, 34.91941],
304
+ [34.975933, 34.48586, 32.729057],
305
+ ],
306
+ [
307
+ [35.21593, 35.209476, 35.20767],
308
+ [35.224304, 35.209522, 35.20715],
309
+ [35.299217, 35.31244, 35.31555],
310
+ [34.25124, 33.828175, 33.234303],
311
+ ],
312
+ ]
313
+ ],
314
+ dtype=np.float32,
315
+ )
316
+
317
+ expected_zeta = np.array(
318
+ [
319
+ [
320
+ [-0.30468762, -0.29416865, -0.30391693, -0.32985148],
321
+ [-0.34336275, -0.29455253, -0.3718359, -0.36176518],
322
+ [-0.3699948, -0.34693155, -0.41338325, -0.40663475],
323
+ [-0.5534979, -0.5270749, -0.45107934, -0.40699923],
324
+ ]
325
+ ],
326
+ dtype=np.float32,
327
+ )
328
+
329
+ expected_u = np.array(
330
+ [
331
+ [
332
+ [[-0.0, -0.0, -0.0], [-0.0, -0.0, -0.0], [0.0, -0.0, -0.0]],
333
+ [[0.0, -0.0, -0.0], [-0.0, -0.0, -0.0], [-0.0, -0.0, -0.0]],
334
+ [
335
+ [0.0, 0.0, -0.0],
336
+ [0.0, 0.0, -0.0],
337
+ [0.06979556, 0.06167743, -0.02247071],
338
+ ],
339
+ [
340
+ [0.04268532, 0.03889201, 0.03351666],
341
+ [0.04645353, 0.04914769, 0.03673013],
342
+ [0.0211786, 0.03679834, 0.0274788],
343
+ ],
344
+ ]
345
+ ],
346
+ dtype=np.float32,
347
+ )
348
+
349
+ expected_v = np.array(
350
+ [
351
+ [
352
+ [
353
+ [0.0, 0.0, 0.0],
354
+ [0.0, 0.0, -0.0],
355
+ [-0.0, -0.0, -0.0],
356
+ [-0.0, -0.0, -0.0],
357
+ ],
358
+ [
359
+ [-0.0, -0.0, -0.0],
360
+ [-0.0, -0.0, -0.0],
361
+ [-0.03831354, -0.02400788, -0.03179555],
362
+ [-0.0, -0.0, -0.0],
363
+ ],
364
+ [
365
+ [-0.00951457, -0.00576979, -0.02147919],
366
+ [-0.0, -0.0, -0.0],
367
+ [0.01915873, 0.02625698, 0.01757628],
368
+ [-0.06720348, -0.08354441, -0.13835917],
369
+ ],
370
+ ]
371
+ ],
372
+ dtype=np.float32,
373
+ )
374
+
375
+ expected_ubar = np.array(
376
+ [
377
+ [
378
+ [0.0, 0.0, 0.0],
379
+ [0.0, 0.0, 0.0],
380
+ [0.0, 0.0, 0.04028399],
381
+ [0.03866891, 0.04446249, 0.02812303],
382
+ ]
383
+ ],
384
+ dtype=np.float32,
385
+ )
386
+
387
+ expected_vbar = np.array(
388
+ [
389
+ [
390
+ [0.0, 0.0, 0.0, 0.0],
391
+ [0.0, 0.0, -0.03169237, 0.0],
392
+ [-0.01189703, 0.0, 0.02102064, -0.09326097],
393
+ ]
394
+ ],
395
+ dtype=np.float32,
396
+ )
397
+
398
+ expected_alk = np.array(
399
+ [
400
+ [
401
+ [
402
+ [2341.926, 2340.8894, 2340.557],
403
+ [2317.8875, 2315.86, 2315.2148],
404
+ [2297.689, 2285.8933, 2284.404],
405
+ [2276.4216, 2258.4436, 2256.1062],
406
+ ],
407
+ [
408
+ [2330.5837, 2329.8225, 2329.5264],
409
+ [2317.878, 2316.6787, 2316.3088],
410
+ [2278.9314, 2269.464, 2268.904],
411
+ [2259.975, 2247.7456, 2246.0632],
412
+ ],
413
+ [
414
+ [2376.7534, 2373.4402, 2372.9192],
415
+ [2362.5308, 2360.5066, 2360.2224],
416
+ [2350.3384, 2344.3135, 2343.6768],
417
+ [2310.4275, 2287.6785, 2281.5872],
418
+ ],
419
+ [
420
+ [2384.8064, 2386.2126, 2386.632],
421
+ [2383.737, 2385.1553, 2385.6685],
422
+ [2380.2297, 2381.4849, 2381.8616],
423
+ [2350.0762, 2342.5403, 2339.2244],
424
+ ],
425
+ ]
426
+ ],
427
+ dtype=np.float32,
428
+ )
429
+
430
+ # Check the values in the dataset
431
+ assert np.allclose(
432
+ initial_conditions_with_bgc_from_climatology.ds["temp"].values, expected_temp
433
+ )
434
+ assert np.allclose(
435
+ initial_conditions_with_bgc_from_climatology.ds["salt"].values, expected_salt
436
+ )
437
+ assert np.allclose(
438
+ initial_conditions_with_bgc_from_climatology.ds["zeta"].values, expected_zeta
439
+ )
440
+ assert np.allclose(
441
+ initial_conditions_with_bgc_from_climatology.ds["u"].values, expected_u
442
+ )
443
+ assert np.allclose(
444
+ initial_conditions_with_bgc_from_climatology.ds["v"].values, expected_v
445
+ )
446
+ assert np.allclose(
447
+ initial_conditions_with_bgc_from_climatology.ds["ubar"].values, expected_ubar
448
+ )
449
+ assert np.allclose(
450
+ initial_conditions_with_bgc_from_climatology.ds["vbar"].values, expected_vbar
451
+ )
452
+ assert np.allclose(
453
+ initial_conditions_with_bgc_from_climatology.ds["ALK"].values, expected_alk
454
+ )
455
+
456
+ initial_conditions_with_bgc_from_climatology.plot(varname="temp", s=0)
457
+ initial_conditions_with_bgc_from_climatology.plot(varname="temp", eta=0)
458
+ initial_conditions_with_bgc_from_climatology.plot(varname="temp", xi=0)
459
+ initial_conditions_with_bgc_from_climatology.plot(varname="temp", s=0, xi=0)
460
+ initial_conditions_with_bgc_from_climatology.plot(varname="temp", eta=0, xi=0)
461
+ initial_conditions_with_bgc_from_climatology.plot(varname="zeta")
462
+ initial_conditions_with_bgc_from_climatology.plot(varname="ALK", s=0, xi=0)
463
+ initial_conditions_with_bgc_from_climatology.plot(varname="ALK", eta=0, xi=0)
464
+
465
+ filepath = tmp_path / "initial_conditions.nc"
466
+ initial_conditions_with_bgc_from_climatology.save(filepath)
467
+ assert filepath.exists()
468
+
469
+
470
+ def test_roundtrip_yaml(initial_conditions):
471
+ """Test that creating an InitialConditions object, saving its parameters to yaml file, and re-opening yaml file creates the same object."""
472
+
473
+ # Create a temporary file
474
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
475
+ filepath = tmpfile.name
476
+
477
+ try:
478
+ initial_conditions.to_yaml(filepath)
479
+
480
+ initial_conditions_from_file = InitialConditions.from_yaml(filepath)
481
+
482
+ assert initial_conditions == initial_conditions_from_file
483
+
484
+ finally:
485
+ os.remove(filepath)
486
+
487
+
488
+ def test_from_yaml_missing_initial_conditions():
489
+ yaml_content = textwrap.dedent(
490
+ """\
491
+ ---
492
+ roms_tools_version: 0.0.0
493
+ ---
494
+ Grid:
495
+ nx: 100
496
+ ny: 100
497
+ size_x: 1800
498
+ size_y: 2400
499
+ center_lon: -10
500
+ center_lat: 61
501
+ rot: -20
502
+ topography_source: ETOPO5
503
+ smooth_factor: 8
504
+ hmin: 5.0
505
+ rmax: 0.2
506
+ """
507
+ )
508
+
509
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
510
+ yaml_filepath = tmp_file.name
511
+ tmp_file.write(yaml_content.encode())
512
+
513
+ try:
514
+ with pytest.raises(
515
+ ValueError,
516
+ match="No InitialConditions configuration found in the YAML file.",
517
+ ):
518
+ InitialConditions.from_yaml(yaml_filepath)
519
+ finally:
520
+ os.remove(yaml_filepath)