roms-tools 0.20__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.
@@ -2,7 +2,7 @@ import pytest
2
2
  from datetime import datetime
3
3
  import numpy as np
4
4
  import xarray as xr
5
- from roms_tools.setup.datasets import Dataset
5
+ from roms_tools.setup.datasets import Dataset, ERA5Correction
6
6
  import tempfile
7
7
  import os
8
8
 
@@ -123,7 +123,7 @@ def test_select_times(data_fixture, expected_time_values, request):
123
123
  # Instantiate Dataset object using the temporary file
124
124
  dataset = Dataset(
125
125
  filename=filepath,
126
- var_names=["var"],
126
+ var_names={"var": "var"},
127
127
  start_time=start_time,
128
128
  end_time=end_time,
129
129
  )
@@ -158,7 +158,9 @@ def test_select_times_no_end_time(data_fixture, expected_time_values, request):
158
158
  dataset.to_netcdf(filepath)
159
159
  try:
160
160
  # Instantiate Dataset object using the temporary file
161
- dataset = Dataset(filename=filepath, var_names=["var"], start_time=start_time)
161
+ dataset = Dataset(
162
+ filename=filepath, var_names={"var": "var"}, start_time=start_time
163
+ )
162
164
 
163
165
  assert dataset.ds is not None
164
166
  assert len(dataset.ds.time) == len(expected_time_values)
@@ -184,7 +186,7 @@ def test_multiple_matching_times(global_dataset_with_multiple_times_per_day):
184
186
  ValueError,
185
187
  match="There must be exactly one time matching the start_time. Found 2 matching times.",
186
188
  ):
187
- Dataset(filename=filepath, var_names=["var"], start_time=start_time)
189
+ Dataset(filename=filepath, var_names={"var": "var"}, start_time=start_time)
188
190
  finally:
189
191
  os.remove(filepath)
190
192
 
@@ -205,7 +207,7 @@ def test_no_matching_times(global_dataset):
205
207
  with pytest.raises(ValueError, match="No matching times found."):
206
208
  Dataset(
207
209
  filename=filepath,
208
- var_names=["var"],
210
+ var_names={"var": "var"},
209
211
  start_time=start_time,
210
212
  end_time=end_time,
211
213
  )
@@ -228,7 +230,7 @@ def test_reverse_latitude_choose_subdomain_negative_depth(global_dataset):
228
230
  # Instantiate Dataset object using the temporary file
229
231
  dataset = Dataset(
230
232
  filename=filepath,
231
- var_names=["var"],
233
+ var_names={"var": "var"},
232
234
  dim_names={
233
235
  "latitude": "latitude",
234
236
  "longitude": "longitude",
@@ -253,7 +255,7 @@ def test_reverse_latitude_choose_subdomain_negative_depth(global_dataset):
253
255
  # test choosing subdomain for domain that does not straddle the dateline
254
256
  dataset = Dataset(
255
257
  filename=filepath,
256
- var_names=["var"],
258
+ var_names={"var": "var"},
257
259
  dim_names={
258
260
  "latitude": "latitude",
259
261
  "longitude": "longitude",
@@ -285,7 +287,7 @@ def test_check_if_global_with_global_dataset(global_dataset):
285
287
  filepath = tmpfile.name
286
288
  global_dataset.to_netcdf(filepath)
287
289
  try:
288
- dataset = Dataset(filename=filepath, var_names=["var"])
290
+ dataset = Dataset(filename=filepath, var_names={"var": "var"})
289
291
  is_global = dataset.check_if_global(dataset.ds)
290
292
  assert is_global
291
293
  finally:
@@ -298,9 +300,71 @@ def test_check_if_global_with_non_global_dataset(non_global_dataset):
298
300
  filepath = tmpfile.name
299
301
  non_global_dataset.to_netcdf(filepath)
300
302
  try:
301
- dataset = Dataset(filename=filepath, var_names=["var"])
303
+ dataset = Dataset(filename=filepath, var_names={"var": "var"})
302
304
  is_global = dataset.check_if_global(dataset.ds)
303
305
 
304
306
  assert not is_global
305
307
  finally:
306
308
  os.remove(filepath)
309
+
310
+
311
+ def test_check_dataset(global_dataset):
312
+
313
+ ds = global_dataset.copy()
314
+ ds = ds.drop_vars("var")
315
+
316
+ # Create a temporary file
317
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
318
+ filepath = tmpfile.name
319
+ ds.to_netcdf(filepath)
320
+ try:
321
+ # Instantiate Dataset object using the temporary file
322
+ start_time = datetime(2022, 2, 1)
323
+ end_time = datetime(2022, 3, 1)
324
+ with pytest.raises(
325
+ ValueError, match="Dataset does not contain all required variables."
326
+ ):
327
+
328
+ Dataset(
329
+ filename=filepath,
330
+ var_names={"var": "var"},
331
+ start_time=start_time,
332
+ end_time=end_time,
333
+ )
334
+ finally:
335
+ os.remove(filepath)
336
+
337
+ ds = global_dataset.copy()
338
+ ds = ds.rename({"latitude": "lat", "longitude": "long"})
339
+
340
+ # Create a temporary file
341
+ with tempfile.NamedTemporaryFile(delete=False) as tmpfile:
342
+ filepath = tmpfile.name
343
+ ds.to_netcdf(filepath)
344
+ try:
345
+ # Instantiate Dataset object using the temporary file
346
+ start_time = datetime(2022, 2, 1)
347
+ end_time = datetime(2022, 3, 1)
348
+ with pytest.raises(
349
+ ValueError, match="Dataset does not contain all required dimensions."
350
+ ):
351
+
352
+ Dataset(
353
+ filename=filepath,
354
+ var_names={"var": "var"},
355
+ start_time=start_time,
356
+ end_time=end_time,
357
+ )
358
+ finally:
359
+ os.remove(filepath)
360
+
361
+
362
+ def test_era5_correction_choose_subdomain():
363
+
364
+ data = ERA5Correction()
365
+ lats = data.ds.latitude[10:20]
366
+ lons = data.ds.longitude[10:20]
367
+ coords = {"latitude": lats, "longitude": lons}
368
+ data.choose_subdomain(coords, straddle=False)
369
+ assert (data.ds["latitude"] == lats).all()
370
+ assert (data.ds["longitude"] == lons).all()
@@ -6,7 +6,8 @@ import numpy as np
6
6
  import tempfile
7
7
  import os
8
8
  import textwrap
9
- from roms_tools.setup.datasets import download_test_data
9
+ from roms_tools.setup.download import download_test_data
10
+ from roms_tools.setup.datasets import CESMBGCDataset
10
11
 
11
12
 
12
13
  @pytest.fixture
@@ -49,37 +50,205 @@ def initial_conditions(example_grid, example_vertical_coordinate):
49
50
  grid=example_grid,
50
51
  vertical_coordinate=example_vertical_coordinate,
51
52
  ini_time=datetime(2021, 6, 29),
52
- filename=fname,
53
+ physics_source={"path": fname, "name": "GLORYS"},
53
54
  )
54
55
 
55
56
 
56
- def test_initial_conditions_creation(initial_conditions):
57
+ @pytest.fixture
58
+ def initial_conditions_with_bgc(example_grid, example_vertical_coordinate):
57
59
  """
58
- Test the creation of the InitialConditions object.
60
+ Fixture for creating a dummy InitialConditions object.
59
61
  """
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"
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
+ )
63
73
 
64
74
 
65
- def test_initial_conditions_ds_attribute(initial_conditions):
75
+ @pytest.fixture
76
+ def initial_conditions_with_bgc_from_climatology(
77
+ example_grid, example_vertical_coordinate
78
+ ):
66
79
  """
67
- Test the ds attribute of the InitialConditions object.
80
+ Fixture for creating a dummy InitialConditions object.
68
81
  """
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
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
+ )
75
97
 
76
98
 
77
- def test_initial_conditions_data_consistency_plot_save(initial_conditions, tmp_path):
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
+ ):
78
247
  """
79
248
  Test that the data within the InitialConditions object remains consistent.
80
249
  Also test plot and save methods in the same test since we dask arrays are already computed.
81
250
  """
82
- initial_conditions.ds.load()
251
+ initial_conditions_with_bgc_from_climatology.ds.load()
83
252
 
84
253
  # Define the expected data
85
254
  expected_temp = np.array(
@@ -226,24 +395,75 @@ def test_initial_conditions_data_consistency_plot_save(initial_conditions, tmp_p
226
395
  dtype=np.float32,
227
396
  )
228
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
+
229
430
  # 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")
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)
244
464
 
245
465
  filepath = tmp_path / "initial_conditions.nc"
246
- initial_conditions.save(filepath)
466
+ initial_conditions_with_bgc_from_climatology.save(filepath)
247
467
  assert filepath.exists()
248
468
 
249
469