roms-tools 3.1.2__py3-none-any.whl → 3.2.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.
- roms_tools/__init__.py +3 -0
- roms_tools/analysis/cdr_analysis.py +203 -0
- roms_tools/analysis/cdr_ensemble.py +198 -0
- roms_tools/analysis/roms_output.py +80 -46
- roms_tools/data/grids/GLORYS_global_grid.nc +0 -0
- roms_tools/download.py +4 -0
- roms_tools/plot.py +75 -21
- roms_tools/setup/boundary_forcing.py +44 -19
- roms_tools/setup/cdr_forcing.py +122 -8
- roms_tools/setup/cdr_release.py +161 -8
- roms_tools/setup/datasets.py +626 -340
- roms_tools/setup/grid.py +138 -137
- roms_tools/setup/initial_conditions.py +113 -48
- roms_tools/setup/mask.py +63 -7
- roms_tools/setup/nesting.py +67 -42
- roms_tools/setup/river_forcing.py +45 -19
- roms_tools/setup/surface_forcing.py +4 -6
- roms_tools/setup/tides.py +1 -2
- roms_tools/setup/topography.py +4 -4
- roms_tools/setup/utils.py +134 -22
- roms_tools/tests/test_analysis/test_cdr_analysis.py +144 -0
- roms_tools/tests/test_analysis/test_cdr_ensemble.py +202 -0
- roms_tools/tests/test_analysis/test_roms_output.py +61 -3
- roms_tools/tests/test_setup/test_boundary_forcing.py +54 -52
- roms_tools/tests/test_setup/test_cdr_forcing.py +54 -0
- roms_tools/tests/test_setup/test_cdr_release.py +118 -1
- roms_tools/tests/test_setup/test_datasets.py +392 -44
- roms_tools/tests/test_setup/test_grid.py +222 -115
- roms_tools/tests/test_setup/test_initial_conditions.py +94 -41
- roms_tools/tests/test_setup/test_surface_forcing.py +2 -1
- roms_tools/tests/test_setup/test_utils.py +91 -1
- roms_tools/tests/test_setup/utils.py +71 -0
- roms_tools/tests/test_tiling/test_join.py +241 -0
- roms_tools/tests/test_utils.py +139 -17
- roms_tools/tiling/join.py +189 -0
- roms_tools/utils.py +131 -99
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/METADATA +12 -2
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/RECORD +41 -33
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/WHEEL +0 -0
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-3.1.2.dist-info → roms_tools-3.2.0.dist-info}/top_level.txt +0 -0
|
@@ -121,6 +121,33 @@ def grid_that_straddles_180_degree_meridian_with_global_srtm15_data():
|
|
|
121
121
|
return grid
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
@pytest.fixture()
|
|
125
|
+
def grid_with_gshhs_coastlines():
|
|
126
|
+
iceland_fjord_kwargs = {
|
|
127
|
+
"nx": 80,
|
|
128
|
+
"ny": 40,
|
|
129
|
+
"size_x": 40,
|
|
130
|
+
"size_y": 20,
|
|
131
|
+
"center_lon": -21.76,
|
|
132
|
+
"center_lat": 64.325,
|
|
133
|
+
"rot": 0,
|
|
134
|
+
"N": 3,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Make sure all 4 L1 files are downloaded
|
|
138
|
+
_ = download_test_data("GSHHS_l_L1.dbf")
|
|
139
|
+
_ = download_test_data("GSHHS_l_L1.prj")
|
|
140
|
+
_ = download_test_data("GSHHS_l_L1.shx")
|
|
141
|
+
shapefile = download_test_data("GSHHS_l_L1.shp")
|
|
142
|
+
|
|
143
|
+
grid = Grid(
|
|
144
|
+
**iceland_fjord_kwargs,
|
|
145
|
+
mask_shapefile=shapefile,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return grid
|
|
149
|
+
|
|
150
|
+
|
|
124
151
|
def test_grid_creation(grid):
|
|
125
152
|
assert grid.nx == 1
|
|
126
153
|
assert grid.ny == 1
|
|
@@ -175,11 +202,33 @@ def test_coords_relation(grid_fixture, request):
|
|
|
175
202
|
"grid_that_straddles_180_degree_meridian_with_shifted_global_etopo_data",
|
|
176
203
|
"grid_that_straddles_dateline_with_global_srtm15_data",
|
|
177
204
|
"grid_that_straddles_180_degree_meridian_with_global_srtm15_data",
|
|
205
|
+
"grid_with_gshhs_coastlines",
|
|
178
206
|
],
|
|
179
207
|
)
|
|
180
208
|
def test_successful_initialization_with_topography(grid_fixture, request):
|
|
181
209
|
grid = request.getfixturevalue(grid_fixture)
|
|
182
|
-
|
|
210
|
+
|
|
211
|
+
expected_attrs = [
|
|
212
|
+
"nx",
|
|
213
|
+
"ny",
|
|
214
|
+
"size_x",
|
|
215
|
+
"size_y",
|
|
216
|
+
"center_lon",
|
|
217
|
+
"center_lat",
|
|
218
|
+
"rot",
|
|
219
|
+
"N",
|
|
220
|
+
"theta_s",
|
|
221
|
+
"theta_b",
|
|
222
|
+
"hc",
|
|
223
|
+
"topography_source",
|
|
224
|
+
"hmin",
|
|
225
|
+
"mask_shapefile",
|
|
226
|
+
"verbose",
|
|
227
|
+
"straddle",
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
for attr in expected_attrs:
|
|
231
|
+
assert hasattr(grid, attr), f"Missing attribute: {attr}"
|
|
183
232
|
|
|
184
233
|
|
|
185
234
|
def test_plot(grid_that_straddles_180_degree_meridian):
|
|
@@ -280,6 +329,7 @@ def test_grid_straddle_crosses_meridian():
|
|
|
280
329
|
"grid",
|
|
281
330
|
"grid_that_straddles_dateline_with_shifted_global_etopo_data",
|
|
282
331
|
"grid_that_straddles_dateline_with_global_srtm15_data",
|
|
332
|
+
"grid_with_gshhs_coastlines",
|
|
283
333
|
],
|
|
284
334
|
)
|
|
285
335
|
def test_roundtrip_netcdf(grid_fixture, tmp_path, request):
|
|
@@ -301,8 +351,8 @@ def test_roundtrip_netcdf(grid_fixture, tmp_path, request):
|
|
|
301
351
|
# Load the grid from the file
|
|
302
352
|
grid_from_file = Grid.from_file(filepath.with_suffix(".nc"))
|
|
303
353
|
|
|
304
|
-
# Assert that the initial grid and the loaded grid are equivalent (including the 'ds' attribute)
|
|
305
354
|
assert grid == grid_from_file
|
|
355
|
+
xr.testing.assert_equal(grid.ds, grid_from_file.ds)
|
|
306
356
|
|
|
307
357
|
# Clean up the .nc file
|
|
308
358
|
(filepath.with_suffix(".nc")).unlink()
|
|
@@ -314,6 +364,7 @@ def test_roundtrip_netcdf(grid_fixture, tmp_path, request):
|
|
|
314
364
|
"grid",
|
|
315
365
|
"grid_that_straddles_dateline_with_shifted_global_etopo_data",
|
|
316
366
|
"grid_that_straddles_dateline_with_global_srtm15_data",
|
|
367
|
+
"grid_with_gshhs_coastlines",
|
|
317
368
|
],
|
|
318
369
|
)
|
|
319
370
|
def test_roundtrip_yaml(grid_fixture, tmp_path, request):
|
|
@@ -332,8 +383,8 @@ def test_roundtrip_yaml(grid_fixture, tmp_path, request):
|
|
|
332
383
|
|
|
333
384
|
grid_from_file = Grid.from_yaml(filepath)
|
|
334
385
|
|
|
335
|
-
# Assert that the initial grid and the loaded grid are equivalent (including the 'ds' attribute)
|
|
336
386
|
assert grid == grid_from_file
|
|
387
|
+
xr.testing.assert_equal(grid.ds, grid_from_file.ds)
|
|
337
388
|
|
|
338
389
|
filepath = Path(filepath)
|
|
339
390
|
filepath.unlink()
|
|
@@ -345,6 +396,7 @@ def test_roundtrip_yaml(grid_fixture, tmp_path, request):
|
|
|
345
396
|
"grid",
|
|
346
397
|
"grid_that_straddles_dateline_with_shifted_global_etopo_data",
|
|
347
398
|
"grid_that_straddles_dateline_with_global_srtm15_data",
|
|
399
|
+
"grid_with_gshhs_coastlines",
|
|
348
400
|
],
|
|
349
401
|
)
|
|
350
402
|
def test_roundtrip_from_file_yaml(grid_fixture, tmp_path, request):
|
|
@@ -359,30 +411,33 @@ def test_roundtrip_from_file_yaml(grid_fixture, tmp_path, request):
|
|
|
359
411
|
filepath_yaml = Path(tmp_path / "test.yaml")
|
|
360
412
|
grid_from_file.to_yaml(filepath_yaml)
|
|
361
413
|
|
|
414
|
+
grid_from_yaml = Grid.from_yaml(filepath_yaml)
|
|
415
|
+
|
|
416
|
+
assert grid_from_yaml == grid
|
|
417
|
+
xr.testing.assert_equal(grid.ds, grid_from_yaml.ds)
|
|
418
|
+
|
|
362
419
|
filepath.unlink()
|
|
363
420
|
filepath_yaml.unlink()
|
|
364
421
|
|
|
365
422
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
hmin=5.0,
|
|
378
|
-
)
|
|
423
|
+
@pytest.mark.parametrize(
|
|
424
|
+
"grid_fixture",
|
|
425
|
+
[
|
|
426
|
+
"grid",
|
|
427
|
+
"grid_that_straddles_dateline_with_shifted_global_etopo_data",
|
|
428
|
+
"grid_that_straddles_dateline_with_global_srtm15_data",
|
|
429
|
+
"grid_with_gshhs_coastlines",
|
|
430
|
+
],
|
|
431
|
+
)
|
|
432
|
+
def test_files_have_same_hash(grid_fixture, tmp_path, request):
|
|
433
|
+
grid = request.getfixturevalue(grid_fixture)
|
|
379
434
|
|
|
380
435
|
yaml_filepath = tmp_path / "test_yaml"
|
|
381
436
|
filepath1 = tmp_path / "test1.nc"
|
|
382
437
|
filepath2 = tmp_path / "test2.nc"
|
|
383
438
|
|
|
384
|
-
|
|
385
|
-
|
|
439
|
+
grid.to_yaml(yaml_filepath)
|
|
440
|
+
grid.save(filepath1)
|
|
386
441
|
|
|
387
442
|
grid_from_file = Grid.from_yaml(yaml_filepath)
|
|
388
443
|
grid_from_file.save(filepath2)
|
|
@@ -505,6 +560,9 @@ def test_from_yaml_version_mismatch(tmp_path, caplog):
|
|
|
505
560
|
yaml_filepath.unlink()
|
|
506
561
|
|
|
507
562
|
|
|
563
|
+
# Vertical coordinate tests
|
|
564
|
+
|
|
565
|
+
|
|
508
566
|
def test_invalid_theta_s_value():
|
|
509
567
|
"""Test the validation of the theta_s value."""
|
|
510
568
|
with pytest.raises(ValueError):
|
|
@@ -608,6 +666,152 @@ def test_plot_vertical_coordinate():
|
|
|
608
666
|
grid.plot_vertical_coordinate(eta=-1, xi=0, s=-1)
|
|
609
667
|
|
|
610
668
|
|
|
669
|
+
# Topography tests
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def test_enclosed_regions():
|
|
673
|
+
"""Test that there are only two connected regions, one dry and one wet."""
|
|
674
|
+
grid = Grid(
|
|
675
|
+
nx=100,
|
|
676
|
+
ny=100,
|
|
677
|
+
size_x=1800,
|
|
678
|
+
size_y=2400,
|
|
679
|
+
center_lon=30,
|
|
680
|
+
center_lat=61,
|
|
681
|
+
rot=20,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
reg, nreg = label(grid.ds.mask_rho)
|
|
685
|
+
npt.assert_equal(nreg, 2)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def test_rmax_criterion():
|
|
689
|
+
grid = Grid(
|
|
690
|
+
nx=100,
|
|
691
|
+
ny=100,
|
|
692
|
+
size_x=1800,
|
|
693
|
+
size_y=2400,
|
|
694
|
+
center_lon=30,
|
|
695
|
+
center_lat=61,
|
|
696
|
+
rot=20,
|
|
697
|
+
)
|
|
698
|
+
r_eta, r_xi = _compute_rfactor(grid.ds.h)
|
|
699
|
+
rmax0 = np.max([r_eta.max(), r_xi.max()])
|
|
700
|
+
npt.assert_array_less(rmax0, 0.2)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def test_hmin_criterion_and_update_topography():
|
|
704
|
+
grid = Grid(
|
|
705
|
+
nx=100,
|
|
706
|
+
ny=100,
|
|
707
|
+
size_x=1800,
|
|
708
|
+
size_y=2400,
|
|
709
|
+
center_lon=30,
|
|
710
|
+
center_lat=61,
|
|
711
|
+
rot=20,
|
|
712
|
+
hmin=5.0,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
assert grid.hmin == 5.0
|
|
716
|
+
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
717
|
+
|
|
718
|
+
grid.update_topography(hmin=10.0)
|
|
719
|
+
|
|
720
|
+
assert grid.hmin == 10.0
|
|
721
|
+
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
722
|
+
|
|
723
|
+
# this should not do anything
|
|
724
|
+
grid.update_topography()
|
|
725
|
+
|
|
726
|
+
assert grid.hmin == 10.0
|
|
727
|
+
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
# Mask tests
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def test_update_mask():
|
|
734
|
+
iceland_fjord_kwargs = {
|
|
735
|
+
"nx": 80,
|
|
736
|
+
"ny": 40,
|
|
737
|
+
"size_x": 40,
|
|
738
|
+
"size_y": 20,
|
|
739
|
+
"center_lon": -21.76,
|
|
740
|
+
"center_lat": 64.325,
|
|
741
|
+
"rot": 0,
|
|
742
|
+
"N": 3,
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
# Make sure all 4 L1 files are downloaded
|
|
746
|
+
_ = download_test_data("GSHHS_l_L1.dbf")
|
|
747
|
+
_ = download_test_data("GSHHS_l_L1.prj")
|
|
748
|
+
_ = download_test_data("GSHHS_l_L1.shx")
|
|
749
|
+
shapefile = download_test_data("GSHHS_l_L1.shp")
|
|
750
|
+
|
|
751
|
+
grid = Grid(
|
|
752
|
+
**iceland_fjord_kwargs,
|
|
753
|
+
mask_shapefile=shapefile,
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
assert grid.mask_shapefile == shapefile
|
|
757
|
+
|
|
758
|
+
# Save original mask
|
|
759
|
+
mask_orig = grid.ds.mask_rho.copy()
|
|
760
|
+
|
|
761
|
+
# Update mask (switches to Natural Earth)
|
|
762
|
+
grid.update_mask()
|
|
763
|
+
|
|
764
|
+
assert grid.mask_shapefile is None
|
|
765
|
+
|
|
766
|
+
# New mask after update
|
|
767
|
+
mask_new = grid.ds.mask_rho.copy()
|
|
768
|
+
|
|
769
|
+
assert abs(mask_new - mask_orig).max() == 1, (
|
|
770
|
+
"Mask should change after update_mask()"
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
# Boundary tests
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def test_mask_topography_boundary():
|
|
778
|
+
"""Test that the mask and topography along the grid boundaries (north, south, east,
|
|
779
|
+
west) are identical to the adjacent inland cells.
|
|
780
|
+
"""
|
|
781
|
+
# Create a grid with some land along the northern boundary
|
|
782
|
+
grid = Grid(
|
|
783
|
+
nx=10, ny=10, size_x=1000, size_y=1000, center_lon=-20, center_lat=60, rot=0
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Toopography
|
|
787
|
+
np.testing.assert_array_equal(
|
|
788
|
+
grid.ds.h.isel(eta_rho=0).data, grid.ds.h.isel(eta_rho=1).data
|
|
789
|
+
)
|
|
790
|
+
np.testing.assert_array_equal(
|
|
791
|
+
grid.ds.h.isel(eta_rho=-1).data, grid.ds.h.isel(eta_rho=-2).data
|
|
792
|
+
)
|
|
793
|
+
np.testing.assert_array_equal(
|
|
794
|
+
grid.ds.h.isel(xi_rho=0).data, grid.ds.h.isel(xi_rho=1).data
|
|
795
|
+
)
|
|
796
|
+
np.testing.assert_array_equal(
|
|
797
|
+
grid.ds.h.isel(xi_rho=-1).data, grid.ds.h.isel(xi_rho=-2).data
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
# Mask
|
|
801
|
+
np.testing.assert_array_equal(
|
|
802
|
+
grid.ds.mask_rho.isel(eta_rho=0).data, grid.ds.mask_rho.isel(eta_rho=1).data
|
|
803
|
+
)
|
|
804
|
+
np.testing.assert_array_equal(
|
|
805
|
+
grid.ds.mask_rho.isel(eta_rho=-1).data, grid.ds.mask_rho.isel(eta_rho=-2).data
|
|
806
|
+
)
|
|
807
|
+
np.testing.assert_array_equal(
|
|
808
|
+
grid.ds.mask_rho.isel(xi_rho=0).data, grid.ds.mask_rho.isel(xi_rho=1).data
|
|
809
|
+
)
|
|
810
|
+
np.testing.assert_array_equal(
|
|
811
|
+
grid.ds.mask_rho.isel(xi_rho=-1).data, grid.ds.mask_rho.isel(xi_rho=-2).data
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
|
|
611
815
|
# More Grid.from_file() tests
|
|
612
816
|
|
|
613
817
|
|
|
@@ -723,100 +927,3 @@ def test_from_file_partial_parameters_raises_error(grid, tmp_path):
|
|
|
723
927
|
|
|
724
928
|
with pytest.raises(ValueError, match="must provide all of"):
|
|
725
929
|
Grid.from_file(path, theta_s=5.0)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
# Topography tests
|
|
729
|
-
def test_enclosed_regions():
|
|
730
|
-
"""Test that there are only two connected regions, one dry and one wet."""
|
|
731
|
-
grid = Grid(
|
|
732
|
-
nx=100,
|
|
733
|
-
ny=100,
|
|
734
|
-
size_x=1800,
|
|
735
|
-
size_y=2400,
|
|
736
|
-
center_lon=30,
|
|
737
|
-
center_lat=61,
|
|
738
|
-
rot=20,
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
reg, nreg = label(grid.ds.mask_rho)
|
|
742
|
-
npt.assert_equal(nreg, 2)
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
def test_rmax_criterion():
|
|
746
|
-
grid = Grid(
|
|
747
|
-
nx=100,
|
|
748
|
-
ny=100,
|
|
749
|
-
size_x=1800,
|
|
750
|
-
size_y=2400,
|
|
751
|
-
center_lon=30,
|
|
752
|
-
center_lat=61,
|
|
753
|
-
rot=20,
|
|
754
|
-
)
|
|
755
|
-
r_eta, r_xi = _compute_rfactor(grid.ds.h)
|
|
756
|
-
rmax0 = np.max([r_eta.max(), r_xi.max()])
|
|
757
|
-
npt.assert_array_less(rmax0, 0.2)
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
def test_hmin_criterion():
|
|
761
|
-
grid = Grid(
|
|
762
|
-
nx=100,
|
|
763
|
-
ny=100,
|
|
764
|
-
size_x=1800,
|
|
765
|
-
size_y=2400,
|
|
766
|
-
center_lon=30,
|
|
767
|
-
center_lat=61,
|
|
768
|
-
rot=20,
|
|
769
|
-
hmin=5.0,
|
|
770
|
-
)
|
|
771
|
-
|
|
772
|
-
assert grid.hmin == 5.0
|
|
773
|
-
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
774
|
-
|
|
775
|
-
grid.update_topography(hmin=10.0)
|
|
776
|
-
|
|
777
|
-
assert grid.hmin == 10.0
|
|
778
|
-
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
779
|
-
|
|
780
|
-
# this should not do anything
|
|
781
|
-
grid.update_topography()
|
|
782
|
-
|
|
783
|
-
assert grid.hmin == 10.0
|
|
784
|
-
assert np.less_equal(grid.hmin, grid.ds.h.min())
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
def test_mask_topography_boundary():
|
|
788
|
-
"""Test that the mask and topography along the grid boundaries (north, south, east,
|
|
789
|
-
west) are identical to the adjacent inland cells.
|
|
790
|
-
"""
|
|
791
|
-
# Create a grid with some land along the northern boundary
|
|
792
|
-
grid = Grid(
|
|
793
|
-
nx=10, ny=10, size_x=1000, size_y=1000, center_lon=-20, center_lat=60, rot=0
|
|
794
|
-
)
|
|
795
|
-
|
|
796
|
-
# Toopography
|
|
797
|
-
np.testing.assert_array_equal(
|
|
798
|
-
grid.ds.h.isel(eta_rho=0).data, grid.ds.h.isel(eta_rho=1).data
|
|
799
|
-
)
|
|
800
|
-
np.testing.assert_array_equal(
|
|
801
|
-
grid.ds.h.isel(eta_rho=-1).data, grid.ds.h.isel(eta_rho=-2).data
|
|
802
|
-
)
|
|
803
|
-
np.testing.assert_array_equal(
|
|
804
|
-
grid.ds.h.isel(xi_rho=0).data, grid.ds.h.isel(xi_rho=1).data
|
|
805
|
-
)
|
|
806
|
-
np.testing.assert_array_equal(
|
|
807
|
-
grid.ds.h.isel(xi_rho=-1).data, grid.ds.h.isel(xi_rho=-2).data
|
|
808
|
-
)
|
|
809
|
-
|
|
810
|
-
# Mask
|
|
811
|
-
np.testing.assert_array_equal(
|
|
812
|
-
grid.ds.mask_rho.isel(eta_rho=0).data, grid.ds.mask_rho.isel(eta_rho=1).data
|
|
813
|
-
)
|
|
814
|
-
np.testing.assert_array_equal(
|
|
815
|
-
grid.ds.mask_rho.isel(eta_rho=-1).data, grid.ds.mask_rho.isel(eta_rho=-2).data
|
|
816
|
-
)
|
|
817
|
-
np.testing.assert_array_equal(
|
|
818
|
-
grid.ds.mask_rho.isel(xi_rho=0).data, grid.ds.mask_rho.isel(xi_rho=1).data
|
|
819
|
-
)
|
|
820
|
-
np.testing.assert_array_equal(
|
|
821
|
-
grid.ds.mask_rho.isel(xi_rho=-1).data, grid.ds.mask_rho.isel(xi_rho=-2).data
|
|
822
|
-
)
|
|
@@ -11,7 +11,35 @@ import xarray as xr
|
|
|
11
11
|
from conftest import calculate_data_hash
|
|
12
12
|
from roms_tools import Grid, InitialConditions
|
|
13
13
|
from roms_tools.download import download_test_data
|
|
14
|
-
from roms_tools.setup.datasets import
|
|
14
|
+
from roms_tools.setup.datasets import (
|
|
15
|
+
CESMBGCDataset,
|
|
16
|
+
UnifiedBGCDataset,
|
|
17
|
+
)
|
|
18
|
+
from roms_tools.tests.test_setup.utils import download_regional_and_bigger
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import copernicusmarine # type: ignore
|
|
22
|
+
except ImportError:
|
|
23
|
+
copernicusmarine = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def example_grid():
|
|
28
|
+
grid = Grid(
|
|
29
|
+
nx=2,
|
|
30
|
+
ny=2,
|
|
31
|
+
size_x=500,
|
|
32
|
+
size_y=1000,
|
|
33
|
+
center_lon=0,
|
|
34
|
+
center_lat=55,
|
|
35
|
+
rot=10,
|
|
36
|
+
N=3, # number of vertical levels
|
|
37
|
+
theta_s=5.0, # surface control parameter
|
|
38
|
+
theta_b=2.0, # bottom control parameter
|
|
39
|
+
hc=250.0, # critical depth
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return grid
|
|
15
43
|
|
|
16
44
|
|
|
17
45
|
@pytest.mark.parametrize(
|
|
@@ -25,7 +53,9 @@ from roms_tools.setup.datasets import CESMBGCDataset, UnifiedBGCDataset
|
|
|
25
53
|
"initial_conditions_with_unified_bgc_from_climatology",
|
|
26
54
|
],
|
|
27
55
|
)
|
|
28
|
-
def
|
|
56
|
+
def test_initial_conditions_creation_with_nondefault_glorys_dataset(
|
|
57
|
+
ic_fixture, request
|
|
58
|
+
):
|
|
29
59
|
"""Test the creation of the InitialConditions object."""
|
|
30
60
|
ic = request.getfixturevalue(ic_fixture)
|
|
31
61
|
|
|
@@ -37,12 +67,65 @@ def test_initial_conditions_creation(ic_fixture, request):
|
|
|
37
67
|
}
|
|
38
68
|
assert hasattr(ic.ds, "adjust_depth_for_sea_surface_height")
|
|
39
69
|
assert isinstance(ic.ds, xr.Dataset)
|
|
40
|
-
assert "temp" in ic.ds
|
|
41
|
-
assert "salt" in ic.ds
|
|
42
|
-
assert "u" in ic.ds
|
|
43
|
-
assert "v" in ic.ds
|
|
44
|
-
assert "zeta" in ic.ds
|
|
45
70
|
assert ic.ds.coords["ocean_time"].attrs["units"] == "seconds"
|
|
71
|
+
expected_vars = {"temp", "salt", "u", "v", "zeta", "ubar", "vbar"}
|
|
72
|
+
assert set(ic.ds.data_vars).issuperset(expected_vars)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.stream
|
|
76
|
+
@pytest.mark.use_copernicus
|
|
77
|
+
@pytest.mark.use_dask
|
|
78
|
+
def test_initial_conditions_creation_with_default_glorys_dataset(example_grid: Grid):
|
|
79
|
+
"""Verify the default GLORYS dataset is loaded when a path is not provided."""
|
|
80
|
+
ic = InitialConditions(
|
|
81
|
+
grid=example_grid,
|
|
82
|
+
ini_time=datetime(2021, 6, 29),
|
|
83
|
+
source={"name": "GLORYS"},
|
|
84
|
+
use_dask=True,
|
|
85
|
+
bypass_validation=True,
|
|
86
|
+
)
|
|
87
|
+
expected_vars = {"temp", "salt", "u", "v", "zeta", "ubar", "vbar"}
|
|
88
|
+
assert set(ic.ds.data_vars).issuperset(expected_vars)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.use_copernicus
|
|
92
|
+
@pytest.mark.skipif(copernicusmarine is None, reason="copernicusmarine required")
|
|
93
|
+
@pytest.mark.parametrize(
|
|
94
|
+
"grid_fixture",
|
|
95
|
+
[
|
|
96
|
+
"tiny_grid_that_straddles_dateline",
|
|
97
|
+
"tiny_grid_that_straddles_180_degree_meridian",
|
|
98
|
+
"tiny_rotated_grid",
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
def test_invariance_to_get_glorys_bounds(tmp_path, grid_fixture, use_dask, request):
|
|
102
|
+
ini_time = datetime(2012, 1, 1)
|
|
103
|
+
grid = request.getfixturevalue(grid_fixture)
|
|
104
|
+
regional_file, bigger_regional_file = download_regional_and_bigger(
|
|
105
|
+
tmp_path, grid, ini_time
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
ic_from_regional = InitialConditions(
|
|
109
|
+
grid=grid,
|
|
110
|
+
source={"name": "GLORYS", "path": str(regional_file)},
|
|
111
|
+
ini_time=ini_time,
|
|
112
|
+
use_dask=use_dask,
|
|
113
|
+
)
|
|
114
|
+
ic_from_bigger_regional = InitialConditions(
|
|
115
|
+
grid=grid,
|
|
116
|
+
source={"name": "GLORYS", "path": str(bigger_regional_file)},
|
|
117
|
+
ini_time=ini_time,
|
|
118
|
+
use_dask=use_dask,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Use assert_allclose instead of equals: necessary for grids that straddle the 180° meridian.
|
|
122
|
+
# Copernicus returns data on [-180, 180] by default, but if you request a range
|
|
123
|
+
# like [170, 190], it remaps longitudes. That remapping introduces tiny floating
|
|
124
|
+
# point differences in the longitude coordinate, which will then propagate into further differences once you do regridding.
|
|
125
|
+
# Need to adjust the tolerances for these grids that straddle the 180° meridian.
|
|
126
|
+
xr.testing.assert_allclose(
|
|
127
|
+
ic_from_bigger_regional.ds, ic_from_regional.ds, rtol=1e-4, atol=1e-5
|
|
128
|
+
)
|
|
46
129
|
|
|
47
130
|
|
|
48
131
|
def test_initial_conditions_creation_with_duplicates(use_dask: bool) -> None:
|
|
@@ -67,6 +150,7 @@ def test_initial_conditions_creation_with_duplicates(use_dask: bool) -> None:
|
|
|
67
150
|
grid=grid,
|
|
68
151
|
ini_time=datetime(2012, 1, 1),
|
|
69
152
|
source={"path": [fname1, fname2], "name": "GLORYS"},
|
|
153
|
+
allow_flex_time=True,
|
|
70
154
|
use_dask=use_dask,
|
|
71
155
|
)
|
|
72
156
|
|
|
@@ -74,6 +158,7 @@ def test_initial_conditions_creation_with_duplicates(use_dask: bool) -> None:
|
|
|
74
158
|
grid=grid,
|
|
75
159
|
ini_time=datetime(2012, 1, 1),
|
|
76
160
|
source={"path": [fname1, fname1, fname2], "name": "GLORYS"},
|
|
161
|
+
allow_flex_time=True,
|
|
77
162
|
use_dask=use_dask,
|
|
78
163
|
)
|
|
79
164
|
|
|
@@ -134,28 +219,9 @@ def test_initial_condition_creation_with_bgc(ic_fixture, request):
|
|
|
134
219
|
assert var in ic.ds
|
|
135
220
|
|
|
136
221
|
|
|
137
|
-
@pytest.fixture
|
|
138
|
-
def example_grid():
|
|
139
|
-
grid = Grid(
|
|
140
|
-
nx=2,
|
|
141
|
-
ny=2,
|
|
142
|
-
size_x=500,
|
|
143
|
-
size_y=1000,
|
|
144
|
-
center_lon=0,
|
|
145
|
-
center_lat=55,
|
|
146
|
-
rot=10,
|
|
147
|
-
N=3, # number of vertical levels
|
|
148
|
-
theta_s=5.0, # surface control parameter
|
|
149
|
-
theta_b=2.0, # bottom control parameter
|
|
150
|
-
hc=250.0, # critical depth
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
return grid
|
|
154
|
-
|
|
155
|
-
|
|
156
222
|
# Test initialization with missing 'name' in source
|
|
157
223
|
def test_initial_conditions_missing_physics_name(example_grid, use_dask):
|
|
158
|
-
with pytest.raises(ValueError, match="`source` must include a 'name'
|
|
224
|
+
with pytest.raises(ValueError, match="`source` must include a 'name'"):
|
|
159
225
|
InitialConditions(
|
|
160
226
|
grid=example_grid,
|
|
161
227
|
ini_time=datetime(2021, 6, 29),
|
|
@@ -164,23 +230,10 @@ def test_initial_conditions_missing_physics_name(example_grid, use_dask):
|
|
|
164
230
|
)
|
|
165
231
|
|
|
166
232
|
|
|
167
|
-
# Test initialization with missing 'path' in source
|
|
168
|
-
def test_initial_conditions_missing_physics_path(example_grid, use_dask):
|
|
169
|
-
with pytest.raises(ValueError, match="`source` must include a 'path'."):
|
|
170
|
-
InitialConditions(
|
|
171
|
-
grid=example_grid,
|
|
172
|
-
ini_time=datetime(2021, 6, 29),
|
|
173
|
-
source={"name": "GLORYS"},
|
|
174
|
-
use_dask=use_dask,
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
|
|
178
233
|
# Test initialization with missing 'name' in bgc_source
|
|
179
234
|
def test_initial_conditions_missing_bgc_name(example_grid, use_dask):
|
|
180
235
|
fname = Path(download_test_data("GLORYS_coarse_test_data.nc"))
|
|
181
|
-
with pytest.raises(
|
|
182
|
-
ValueError, match="`bgc_source` must include a 'name' if it is provided."
|
|
183
|
-
):
|
|
236
|
+
with pytest.raises(ValueError, match="`bgc_source` must include a 'name'"):
|
|
184
237
|
InitialConditions(
|
|
185
238
|
grid=example_grid,
|
|
186
239
|
ini_time=datetime(2021, 6, 29),
|
|
@@ -10,6 +10,7 @@ import xarray as xr
|
|
|
10
10
|
from conftest import calculate_data_hash
|
|
11
11
|
from roms_tools import Grid, SurfaceForcing
|
|
12
12
|
from roms_tools.download import download_test_data
|
|
13
|
+
from roms_tools.setup.datasets import RawDataSource
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@pytest.fixture
|
|
@@ -159,7 +160,7 @@ def _test_successful_initialization(
|
|
|
159
160
|
grid: Grid,
|
|
160
161
|
start_time: datetime,
|
|
161
162
|
end_time: datetime,
|
|
162
|
-
source:
|
|
163
|
+
source: RawDataSource,
|
|
163
164
|
coarse_grid_mode: str,
|
|
164
165
|
use_dask: bool,
|
|
165
166
|
caplog,
|