roms-tools 2.2.1__py3-none-any.whl → 2.4.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.
Files changed (152) hide show
  1. ci/environment.yml +1 -0
  2. roms_tools/__init__.py +2 -0
  3. roms_tools/analysis/roms_output.py +590 -0
  4. roms_tools/{setup/download.py → download.py} +3 -0
  5. roms_tools/{setup/plot.py → plot.py} +34 -28
  6. roms_tools/setup/boundary_forcing.py +199 -203
  7. roms_tools/setup/datasets.py +60 -136
  8. roms_tools/setup/grid.py +40 -67
  9. roms_tools/setup/initial_conditions.py +249 -247
  10. roms_tools/setup/nesting.py +6 -27
  11. roms_tools/setup/river_forcing.py +41 -76
  12. roms_tools/setup/surface_forcing.py +125 -75
  13. roms_tools/setup/tides.py +31 -51
  14. roms_tools/setup/topography.py +1 -1
  15. roms_tools/setup/utils.py +44 -224
  16. roms_tools/tests/test_analysis/test_roms_output.py +269 -0
  17. roms_tools/tests/{test_setup/test_regrid.py → test_regrid.py} +1 -1
  18. roms_tools/tests/test_setup/test_boundary_forcing.py +221 -58
  19. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/.zattrs +5 -3
  20. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/.zmetadata +156 -121
  21. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/abs_time/.zarray +2 -2
  22. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/abs_time/.zattrs +2 -1
  23. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/abs_time/0 +0 -0
  24. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/bry_time/.zarray +2 -2
  25. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/bry_time/.zattrs +1 -1
  26. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/bry_time/0 +0 -0
  27. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/.zarray +4 -4
  28. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/0.0.0 +0 -0
  29. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/.zarray +4 -4
  30. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/0.0.0 +0 -0
  31. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/.zarray +4 -4
  32. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/0.0.0 +0 -0
  33. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/.zarray +4 -4
  34. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/0.0.0 +0 -0
  35. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/.zarray +4 -4
  36. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/0.0.0 +0 -0
  37. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/.zarray +4 -4
  38. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/0.0.0 +0 -0
  39. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/.zarray +4 -4
  40. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/0.0.0 +0 -0
  41. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/.zarray +4 -4
  42. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/0.0.0 +0 -0
  43. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/.zarray +4 -4
  44. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/0.0.0 +0 -0
  45. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/.zarray +4 -4
  46. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/0.0.0 +0 -0
  47. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/.zarray +4 -4
  48. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/0.0.0 +0 -0
  49. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/.zarray +4 -4
  50. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/0.0.0 +0 -0
  51. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/.zarray +4 -4
  52. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/0.0 +0 -0
  53. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/.zarray +4 -4
  54. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/0.0 +0 -0
  55. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/.zarray +4 -4
  56. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/0.0 +0 -0
  57. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/.zarray +4 -4
  58. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/0.0 +0 -0
  59. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/.zarray +4 -4
  60. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/0.0.0 +0 -0
  61. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/.zarray +4 -4
  62. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/0.0.0 +0 -0
  63. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/.zarray +4 -4
  64. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/0.0.0 +0 -0
  65. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/.zarray +4 -4
  66. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/0.0.0 +0 -0
  67. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/.zarray +4 -4
  68. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/0.0 +0 -0
  69. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/.zarray +4 -4
  70. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/0.0 +0 -0
  71. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/.zarray +4 -4
  72. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/0.0 +0 -0
  73. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/.zarray +4 -4
  74. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/0.0 +0 -0
  75. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_east/.zarray +4 -4
  76. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_east/.zattrs +8 -0
  77. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_east/0.0 +0 -0
  78. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_north/.zarray +4 -4
  79. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_north/.zattrs +8 -0
  80. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_north/0.0 +0 -0
  81. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_south/.zarray +4 -4
  82. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_south/.zattrs +8 -0
  83. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_south/0.0 +0 -0
  84. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_west/.zarray +4 -4
  85. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_west/.zattrs +8 -0
  86. roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_west/0.0 +0 -0
  87. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +4 -4
  88. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +4 -4
  89. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle/0.0 +0 -0
  90. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle_coarse/0.0 +0 -0
  91. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/f/0.0 +0 -0
  92. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
  93. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_coarse/0.0 +0 -0
  94. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_rho/0.0 +0 -0
  95. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_u/0.0 +0 -0
  96. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_v/0.0 +0 -0
  97. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_coarse/0.0 +0 -0
  98. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_rho/0.0 +0 -0
  99. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_u/0.0 +0 -0
  100. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_v/0.0 +0 -0
  101. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
  102. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
  103. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
  104. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
  105. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pm/0.0 +0 -0
  106. roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pn/0.0 +0 -0
  107. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zattrs +2 -1
  108. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zmetadata +6 -4
  109. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Cs_r/.zattrs +1 -1
  110. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Cs_w/.zattrs +1 -1
  111. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/0.0.0.0 +0 -0
  112. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/0.0.0.0 +0 -0
  113. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/0.0.0.0 +0 -0
  114. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/abs_time/.zattrs +1 -0
  115. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/0.0.0.0 +0 -0
  116. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ocean_time/.zattrs +1 -1
  117. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/0.0.0.0 +0 -0
  118. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/0.0.0.0 +0 -0
  119. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/0.0.0.0 +0 -0
  120. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/0.0.0.0 +0 -0
  121. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/0.0.0.0 +0 -0
  122. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/0.0.0 +0 -0
  123. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/0.0.0.0 +0 -0
  124. roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/0.0.0 +0 -0
  125. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +30 -0
  126. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zarray +22 -0
  127. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zattrs +8 -0
  128. roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/0.0 +0 -0
  129. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +30 -0
  130. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zarray +22 -0
  131. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zattrs +8 -0
  132. roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/0.0 +0 -0
  133. roms_tools/tests/test_setup/test_datasets.py +1 -1
  134. roms_tools/tests/test_setup/test_grid.py +1 -14
  135. roms_tools/tests/test_setup/test_initial_conditions.py +205 -67
  136. roms_tools/tests/test_setup/test_nesting.py +0 -16
  137. roms_tools/tests/test_setup/test_river_forcing.py +9 -37
  138. roms_tools/tests/test_setup/test_surface_forcing.py +103 -74
  139. roms_tools/tests/test_setup/test_tides.py +5 -17
  140. roms_tools/tests/test_setup/test_topography.py +1 -1
  141. roms_tools/tests/test_setup/test_utils.py +57 -1
  142. roms_tools/tests/{test_utils.py → test_tiling/test_partition.py} +1 -1
  143. roms_tools/tiling/partition.py +338 -0
  144. roms_tools/utils.py +310 -276
  145. roms_tools/vertical_coordinate.py +227 -0
  146. {roms_tools-2.2.1.dist-info → roms_tools-2.4.0.dist-info}/METADATA +1 -1
  147. {roms_tools-2.2.1.dist-info → roms_tools-2.4.0.dist-info}/RECORD +151 -142
  148. roms_tools/setup/vertical_coordinate.py +0 -109
  149. /roms_tools/{setup/regrid.py → regrid.py} +0 -0
  150. {roms_tools-2.2.1.dist-info → roms_tools-2.4.0.dist-info}/LICENSE +0 -0
  151. {roms_tools-2.2.1.dist-info → roms_tools-2.4.0.dist-info}/WHEEL +0 -0
  152. {roms_tools-2.2.1.dist-info → roms_tools-2.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,269 @@
1
+ import pytest
2
+ from pathlib import Path
3
+ import xarray as xr
4
+ import os
5
+ import logging
6
+ from datetime import datetime
7
+ from roms_tools import Grid, ROMSOutput
8
+ from roms_tools.download import download_test_data
9
+
10
+
11
+ @pytest.fixture
12
+ def roms_output_from_restart_file(use_dask):
13
+
14
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
15
+ grid = Grid.from_file(fname_grid)
16
+
17
+ # Single file
18
+ return ROMSOutput(
19
+ grid=grid,
20
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
21
+ type="restart",
22
+ use_dask=use_dask,
23
+ )
24
+
25
+
26
+ def test_load_model_output_file(roms_output_from_restart_file, use_dask):
27
+
28
+ assert isinstance(roms_output_from_restart_file.ds, xr.Dataset)
29
+
30
+
31
+ def test_load_model_output_directory(use_dask):
32
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
33
+ grid = Grid.from_file(fname_grid)
34
+
35
+ # Download at least two files, so these will be found within the pooch directory
36
+ _ = Path(download_test_data("eastpac25km_rst.19980106000000.nc"))
37
+ _ = Path(download_test_data("eastpac25km_rst.19980126000000.nc"))
38
+
39
+ # Directory
40
+ directory = os.path.dirname(download_test_data("eastpac25km_rst.19980106000000.nc"))
41
+ output = ROMSOutput(grid=grid, path=directory, type="restart", use_dask=use_dask)
42
+ assert isinstance(output.ds, xr.Dataset)
43
+
44
+
45
+ def test_load_model_output_file_list(use_dask):
46
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
47
+ grid = Grid.from_file(fname_grid)
48
+
49
+ # List of files
50
+ file1 = Path(download_test_data("eastpac25km_rst.19980106000000.nc"))
51
+ file2 = Path(download_test_data("eastpac25km_rst.19980126000000.nc"))
52
+ output = ROMSOutput(
53
+ grid=grid, path=[file1, file2], type="restart", use_dask=use_dask
54
+ )
55
+ assert isinstance(output.ds, xr.Dataset)
56
+
57
+
58
+ def test_invalid_type(use_dask):
59
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
60
+ grid = Grid.from_file(fname_grid)
61
+
62
+ # Invalid type
63
+ with pytest.raises(ValueError, match="Invalid type 'invalid_type'"):
64
+ ROMSOutput(
65
+ grid=grid,
66
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
67
+ type="invalid_type",
68
+ use_dask=use_dask,
69
+ )
70
+
71
+
72
+ def test_invalid_path(use_dask):
73
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
74
+ grid = Grid.from_file(fname_grid)
75
+
76
+ # Non-existent file
77
+ with pytest.raises(FileNotFoundError):
78
+ ROMSOutput(
79
+ grid=grid,
80
+ path=Path("/path/to/nonexistent/file.nc"),
81
+ type="restart",
82
+ use_dask=use_dask,
83
+ )
84
+
85
+ # Non-existent directory
86
+ with pytest.raises(FileNotFoundError):
87
+ ROMSOutput(
88
+ grid=grid,
89
+ path=Path("/path/to/nonexistent/directory"),
90
+ type="restart",
91
+ use_dask=use_dask,
92
+ )
93
+
94
+
95
+ def test_set_correct_model_reference_date(use_dask):
96
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
97
+ grid = Grid.from_file(fname_grid)
98
+
99
+ output = ROMSOutput(
100
+ grid=grid,
101
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
102
+ type="restart",
103
+ use_dask=use_dask,
104
+ )
105
+ assert output.model_reference_date == datetime(1995, 1, 1)
106
+
107
+
108
+ def test_model_reference_date_mismatch(use_dask):
109
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
110
+ grid = Grid.from_file(fname_grid)
111
+
112
+ # Create a ROMSOutput with a specified model_reference_date
113
+ model_ref_date = datetime(2020, 1, 1)
114
+ with pytest.raises(
115
+ ValueError, match="Mismatch between `self.model_reference_date`"
116
+ ):
117
+ ROMSOutput(
118
+ grid=grid,
119
+ path=Path(download_test_data("eastpac25km_rst.19980106000000.nc")),
120
+ type="restart",
121
+ model_reference_date=model_ref_date,
122
+ use_dask=use_dask,
123
+ )
124
+
125
+
126
+ def test_model_reference_date_no_metadata(use_dask, tmp_path, caplog):
127
+ # Helper function to handle the test logic for cases where metadata is missing or invalid
128
+ def test_no_metadata(faulty_ocean_time_attr, expected_exception, log_message=None):
129
+ ds = xr.open_dataset(fname)
130
+ ds["ocean_time"].attrs = faulty_ocean_time_attr
131
+
132
+ # Write modified dataset to a new file
133
+ fname_mod = tmp_path / "eastpac25km_rst.19980106000000_without_metadata.nc"
134
+ ds.to_netcdf(fname_mod)
135
+
136
+ # Test case 1: Expecting a ValueError when metadata is missing or invalid
137
+ with pytest.raises(
138
+ expected_exception,
139
+ match="Model reference date could not be inferred from the metadata",
140
+ ):
141
+ ROMSOutput(grid=grid, path=fname_mod, type="restart", use_dask=use_dask)
142
+
143
+ # Test case 2: When a model reference date is explicitly set, verify the warning
144
+ with caplog.at_level(logging.WARNING):
145
+ ROMSOutput(
146
+ grid=grid,
147
+ path=fname_mod,
148
+ model_reference_date=datetime(1995, 1, 1),
149
+ type="restart",
150
+ use_dask=use_dask,
151
+ )
152
+
153
+ if log_message:
154
+ # Verify the warning message in the log
155
+ assert log_message in caplog.text
156
+
157
+ fname_mod.unlink()
158
+
159
+ # Load grid and test data
160
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
161
+ grid = Grid.from_file(fname_grid)
162
+ fname = download_test_data("eastpac25km_rst.19980106000000.nc")
163
+
164
+ # Test 1: Ocean time attribute 'long_name' is missing
165
+ test_no_metadata({}, ValueError)
166
+
167
+ # Test 2: Ocean time attribute 'long_name' contains invalid information
168
+ test_no_metadata(
169
+ {"long_name": "some random text"},
170
+ ValueError,
171
+ "Could not infer the model reference date from the metadata.",
172
+ )
173
+
174
+
175
+ def test_compute_depth_coordinates(use_dask):
176
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
177
+ grid = Grid.from_file(fname_grid)
178
+
179
+ fname_restart1 = Path(download_test_data("eastpac25km_rst.19980106000000.nc"))
180
+ output = ROMSOutput(
181
+ grid=grid, path=fname_restart1, type="restart", use_dask=use_dask
182
+ )
183
+
184
+ # Before calling get_vertical_coordinates, check if the dataset doesn't already have depth coordinates
185
+ assert "layer_depth_rho" not in output.ds.data_vars
186
+
187
+ # Call the method to get vertical coordinates
188
+ output.compute_depth_coordinates(depth_type="layer")
189
+
190
+ # Check if the depth coordinates were added
191
+ assert "layer_depth_rho" in output.ds.data_vars
192
+
193
+
194
+ def test_check_vertical_coordinate_mismatch(use_dask):
195
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
196
+ grid = Grid.from_file(fname_grid)
197
+
198
+ fname_restart1 = Path(download_test_data("eastpac25km_rst.19980106000000.nc"))
199
+ output = ROMSOutput(
200
+ grid=grid, path=fname_restart1, type="restart", use_dask=use_dask
201
+ )
202
+
203
+ # create a mock dataset with inconsistent vertical coordinate parameters
204
+ ds_mock = output.ds.copy()
205
+
206
+ # Modify one of the vertical coordinate attributes to cause a mismatch
207
+ ds_mock.attrs["theta_s"] = 999
208
+
209
+ # Check if ValueError is raised due to mismatch
210
+ with pytest.raises(ValueError, match="theta_s from grid"):
211
+ output._check_vertical_coordinate(ds_mock)
212
+
213
+ # create a mock dataset with inconsistent vertical coordinate parameters
214
+ ds_mock = output.ds.copy()
215
+
216
+ # Modify one of the vertical coordinate attributes to cause a mismatch
217
+ ds_mock.attrs["Cs_w"] = ds_mock.attrs["Cs_w"] + 0.01
218
+
219
+ # Check if ValueError is raised due to mismatch
220
+ with pytest.raises(ValueError, match="Cs_w from grid"):
221
+ output._check_vertical_coordinate(ds_mock)
222
+
223
+
224
+ def test_that_coordinates_are_added(use_dask):
225
+ fname_grid = Path(download_test_data("epac25km_grd.nc"))
226
+ grid = Grid.from_file(fname_grid)
227
+
228
+ fname_restart1 = Path(download_test_data("eastpac25km_rst.19980106000000.nc"))
229
+ output = ROMSOutput(
230
+ grid=grid, path=fname_restart1, type="restart", use_dask=use_dask
231
+ )
232
+
233
+ assert "abs_time" in output.ds.coords
234
+ assert "lat_rho" in output.ds.coords
235
+ assert "lon_rho" in output.ds.coords
236
+
237
+
238
+ def test_plot(roms_output_from_restart_file, use_dask):
239
+
240
+ kwargs = {}
241
+ for var_name in ["temp", "u", "v"]:
242
+ roms_output_from_restart_file.plot(var_name, time=0, s=-1, **kwargs)
243
+ roms_output_from_restart_file.plot(var_name, time=0, eta=0, **kwargs)
244
+ roms_output_from_restart_file.plot(var_name, time=0, xi=0, **kwargs)
245
+ roms_output_from_restart_file.plot(var_name, time=0, eta=0, xi=0, **kwargs)
246
+ roms_output_from_restart_file.plot(var_name, time=0, s=-1, eta=0, **kwargs)
247
+
248
+ kwargs = {"depth_contours": True, "layer_contours": True}
249
+ for var_name in ["temp", "u", "v"]:
250
+ roms_output_from_restart_file.plot(var_name, time=0, s=-1, **kwargs)
251
+ roms_output_from_restart_file.plot(var_name, time=0, eta=0, **kwargs)
252
+ roms_output_from_restart_file.plot(var_name, time=0, xi=0, **kwargs)
253
+ roms_output_from_restart_file.plot(var_name, time=0, eta=0, xi=0, **kwargs)
254
+ roms_output_from_restart_file.plot(var_name, time=0, s=-1, eta=0, **kwargs)
255
+
256
+ roms_output_from_restart_file.plot("zeta", time=0, **kwargs)
257
+ roms_output_from_restart_file.plot("zeta", time=0, eta=0, **kwargs)
258
+ roms_output_from_restart_file.plot("zeta", time=0, xi=0, **kwargs)
259
+
260
+
261
+ def test_plot_errors(roms_output_from_restart_file, use_dask):
262
+ with pytest.raises(ValueError, match="Invalid time index"):
263
+ roms_output_from_restart_file.plot("temp", time=10, s=-1)
264
+ with pytest.raises(ValueError, match="Invalid input"):
265
+ roms_output_from_restart_file.plot("temp", time=0)
266
+ with pytest.raises(ValueError, match="Ambiguous input"):
267
+ roms_output_from_restart_file.plot("temp", time=0, s=-1, eta=0, xi=0)
268
+ with pytest.raises(ValueError, match="Conflicting input"):
269
+ roms_output_from_restart_file.plot("zeta", time=0, eta=0, xi=0)
@@ -1,7 +1,7 @@
1
1
  import pytest
2
2
  import numpy as np
3
3
  import xarray as xr
4
- from roms_tools.setup.regrid import VerticalRegrid
4
+ from roms_tools.regrid import VerticalRegrid
5
5
 
6
6
 
7
7
  def vertical_regridder(depth_values, layer_depth_rho_values):
@@ -1,10 +1,11 @@
1
1
  import pytest
2
2
  from datetime import datetime
3
3
  import xarray as xr
4
+ import numpy as np
4
5
  from roms_tools import Grid, BoundaryForcing
5
6
  import textwrap
6
- from roms_tools.setup.download import download_test_data
7
- from conftest import calculate_file_hash
7
+ from roms_tools.download import download_test_data
8
+ from conftest import calculate_data_hash
8
9
  from pathlib import Path
9
10
  import logging
10
11
 
@@ -13,19 +14,23 @@ import logging
13
14
  "boundary_forcing_fixture",
14
15
  [
15
16
  "boundary_forcing",
16
- # "boundary_forcing_with_2d_fill",
17
+ "boundary_forcing_adjusted_for_zeta",
18
+ "boundary_forcing_with_2d_fill",
19
+ "boundary_forcing_with_2d_fill_adjusted_for_zeta",
17
20
  ],
18
21
  )
19
22
  def test_boundary_forcing_creation(boundary_forcing_fixture, request):
20
23
  """Test the creation of the BoundaryForcing object."""
21
24
 
22
- fname = Path(download_test_data("GLORYS_coarse_test_data.nc"))
23
25
  boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
24
- assert boundary_forcing.start_time == datetime(2021, 6, 29)
25
- assert boundary_forcing.end_time == datetime(2021, 6, 30)
26
+
27
+ fname1 = Path(download_test_data("GLORYS_NA_20120101.nc"))
28
+ fname2 = Path(download_test_data("GLORYS_NA_20121231.nc"))
29
+ assert boundary_forcing.start_time == datetime(2012, 1, 1)
30
+ assert boundary_forcing.end_time == datetime(2012, 12, 31)
26
31
  assert boundary_forcing.source == {
27
32
  "name": "GLORYS",
28
- "path": fname,
33
+ "path": [fname1, fname2],
29
34
  "climatology": False,
30
35
  }
31
36
  assert boundary_forcing.model_reference_date == datetime(2000, 1, 1)
@@ -44,16 +49,17 @@ def test_boundary_forcing_creation(boundary_forcing_fixture, request):
44
49
  assert f"v_{direction}" in boundary_forcing.ds
45
50
  assert f"zeta_{direction}" in boundary_forcing.ds
46
51
 
47
- assert len(boundary_forcing.ds.bry_time) == 1
52
+ assert len(boundary_forcing.ds.bry_time) == 2
48
53
  assert boundary_forcing.ds.coords["bry_time"].attrs["units"] == "days"
49
54
  assert not hasattr(boundary_forcing.ds, "climatology")
55
+ assert hasattr(boundary_forcing.ds, "adjust_depth_for_sea_surface_height")
56
+ assert hasattr(boundary_forcing.ds, "apply_2d_horizontal_fill")
50
57
 
51
58
 
52
59
  @pytest.mark.parametrize(
53
60
  "boundary_forcing_fixture",
54
61
  [
55
62
  "bgc_boundary_forcing_from_climatology",
56
- # "bgc_boundary_forcing_from_climatology_with_2d_fill",
57
63
  ],
58
64
  )
59
65
  def test_boundary_forcing_creation_with_bgc(boundary_forcing_fixture, request):
@@ -155,6 +161,113 @@ def test_boundary_divided_by_land_warning(caplog, use_dask):
155
161
  assert "the western boundary is divided by land" in caplog.text
156
162
 
157
163
 
164
+ def test_info_depth(caplog, use_dask):
165
+
166
+ grid = Grid(
167
+ nx=3,
168
+ ny=3,
169
+ size_x=400,
170
+ size_y=400,
171
+ center_lon=-8,
172
+ center_lat=58,
173
+ rot=0,
174
+ N=3, # number of vertical levels
175
+ theta_s=5.0, # surface control parameter
176
+ theta_b=2.0, # bottom control parameter
177
+ hc=250.0, # critical depth
178
+ )
179
+
180
+ fname1 = Path(download_test_data("GLORYS_NA_20120101.nc"))
181
+ fname2 = Path(download_test_data("GLORYS_NA_20121231.nc"))
182
+
183
+ with caplog.at_level(logging.INFO):
184
+ BoundaryForcing(
185
+ grid=grid,
186
+ start_time=datetime(2012, 1, 1),
187
+ end_time=datetime(2012, 12, 31),
188
+ source={"name": "GLORYS", "path": [fname1, fname2]},
189
+ adjust_depth_for_sea_surface_height=True,
190
+ use_dask=use_dask,
191
+ )
192
+
193
+ # Verify the warning message in the log
194
+ assert "Sea surface height will be used to adjust depth coordinates." in caplog.text
195
+
196
+ # Clear the log before the next test
197
+ caplog.clear()
198
+
199
+ with caplog.at_level(logging.INFO):
200
+
201
+ BoundaryForcing(
202
+ grid=grid,
203
+ start_time=datetime(2012, 1, 1),
204
+ end_time=datetime(2012, 12, 31),
205
+ source={"name": "GLORYS", "path": [fname1, fname2]},
206
+ adjust_depth_for_sea_surface_height=False,
207
+ use_dask=use_dask,
208
+ )
209
+ # Verify the warning message in the log
210
+ assert (
211
+ "Sea surface height will NOT be used to adjust depth coordinates."
212
+ in caplog.text
213
+ )
214
+
215
+
216
+ def test_info_fill(caplog, use_dask):
217
+
218
+ grid = Grid(
219
+ nx=3,
220
+ ny=3,
221
+ size_x=400,
222
+ size_y=400,
223
+ center_lon=-8,
224
+ center_lat=58,
225
+ rot=0,
226
+ N=3, # number of vertical levels
227
+ theta_s=5.0, # surface control parameter
228
+ theta_b=2.0, # bottom control parameter
229
+ hc=250.0, # critical depth
230
+ )
231
+
232
+ fname1 = Path(download_test_data("GLORYS_NA_20120101.nc"))
233
+ fname2 = Path(download_test_data("GLORYS_NA_20121231.nc"))
234
+
235
+ with caplog.at_level(logging.INFO):
236
+ BoundaryForcing(
237
+ grid=grid,
238
+ start_time=datetime(2012, 1, 1),
239
+ end_time=datetime(2012, 12, 31),
240
+ source={"name": "GLORYS", "path": [fname1, fname2]},
241
+ apply_2d_horizontal_fill=True,
242
+ use_dask=use_dask,
243
+ )
244
+
245
+ # Verify the warning message in the log
246
+ assert (
247
+ "Applying 2D horizontal fill to the source data before regridding."
248
+ in caplog.text
249
+ )
250
+
251
+ # Clear the log before the next test
252
+ caplog.clear()
253
+
254
+ with caplog.at_level(logging.INFO):
255
+
256
+ BoundaryForcing(
257
+ grid=grid,
258
+ start_time=datetime(2012, 1, 1),
259
+ end_time=datetime(2012, 12, 31),
260
+ source={"name": "GLORYS", "path": [fname1, fname2]},
261
+ apply_2d_horizontal_fill=False,
262
+ use_dask=use_dask,
263
+ )
264
+ # Verify the warning message in the log
265
+ assert (
266
+ "Applying 1D horizontal fill separately to each regridded boundary."
267
+ in caplog.text
268
+ )
269
+
270
+
158
271
  def test_1d_and_2d_fill_coincide_if_no_land(use_dask):
159
272
 
160
273
  # this grid lies entirely over open ocean
@@ -182,23 +295,95 @@ def test_1d_and_2d_fill_coincide_if_no_land(use_dask):
182
295
  xr.testing.assert_allclose(bf_1d_fill.ds, bf_2d_fill.ds, rtol=1.0e-4)
183
296
 
184
297
 
185
- def test_boundary_forcing_plot(boundary_forcing):
298
+ @pytest.mark.parametrize(
299
+ "boundary_forcing_fixture",
300
+ [
301
+ "boundary_forcing_adjusted_for_zeta",
302
+ "boundary_forcing_with_2d_fill_adjusted_for_zeta",
303
+ ],
304
+ )
305
+ def test_correct_depth_coords_adjusted_for_zeta(
306
+ boundary_forcing_fixture, request, use_dask
307
+ ):
308
+
309
+ boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
310
+
311
+ for direction in ["south", "east", "north", "west"]:
312
+
313
+ # Test that uppermost interface coincides with sea surface height
314
+ assert np.allclose(
315
+ boundary_forcing.ds_depth_coords[f"interface_depth_rho_{direction}"]
316
+ .isel(s_w=-1)
317
+ .values,
318
+ -boundary_forcing.ds[f"zeta_{direction}"].values,
319
+ atol=1e-6,
320
+ )
321
+
322
+
323
+ @pytest.mark.parametrize(
324
+ "boundary_forcing_fixture",
325
+ [
326
+ "boundary_forcing",
327
+ "boundary_forcing_with_2d_fill",
328
+ ],
329
+ )
330
+ def test_correct_depth_coords_zero_zeta(boundary_forcing_fixture, request, use_dask):
331
+
332
+ boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
333
+
334
+ for direction in ["south", "east", "north", "west"]:
335
+
336
+ # Test that uppermost interface coincides with sea surface height
337
+ assert np.allclose(
338
+ boundary_forcing.ds_depth_coords[f"interface_depth_rho_{direction}"]
339
+ .isel(s_w=-1)
340
+ .values,
341
+ 0 * boundary_forcing.ds[f"zeta_{direction}"].values,
342
+ atol=1e-6,
343
+ )
344
+
345
+
346
+ @pytest.mark.parametrize(
347
+ "boundary_forcing_fixture",
348
+ [
349
+ "boundary_forcing",
350
+ "boundary_forcing_with_2d_fill",
351
+ "boundary_forcing_adjusted_for_zeta",
352
+ "boundary_forcing_with_2d_fill_adjusted_for_zeta",
353
+ ],
354
+ )
355
+ def test_boundary_forcing_plot(boundary_forcing_fixture, request):
186
356
  """Test plot."""
357
+ boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
187
358
 
188
- boundary_forcing.plot(var_name="temp_south", layer_contours=True)
189
- boundary_forcing.plot(var_name="temp_east", layer_contours=True)
190
- boundary_forcing.plot(var_name="temp_north", layer_contours=True)
191
- boundary_forcing.plot(var_name="temp_west", layer_contours=True)
192
- boundary_forcing.plot(var_name="zeta_south")
193
- boundary_forcing.plot(var_name="zeta_east")
194
- boundary_forcing.plot(var_name="zeta_north")
195
- boundary_forcing.plot(var_name="zeta_west")
196
- boundary_forcing.plot(var_name="vbar_north")
197
- boundary_forcing.plot(var_name="ubar_west")
359
+ for direction in ["south", "east", "north", "west"]:
360
+ for layer_contours in [False, True]:
361
+ boundary_forcing.plot(
362
+ var_name=f"temp_{direction}", layer_contours=layer_contours
363
+ )
364
+ boundary_forcing.plot(
365
+ var_name=f"u_{direction}", layer_contours=layer_contours
366
+ )
367
+ boundary_forcing.plot(
368
+ var_name=f"v_{direction}", layer_contours=layer_contours
369
+ )
370
+ boundary_forcing.plot(var_name=f"zeta_{direction}")
371
+ boundary_forcing.plot(var_name=f"vbar_{direction}")
372
+ boundary_forcing.plot(var_name=f"ubar_{direction}")
198
373
 
199
374
 
200
- def test_boundary_forcing_save(boundary_forcing, tmp_path):
375
+ @pytest.mark.parametrize(
376
+ "boundary_forcing_fixture",
377
+ [
378
+ "boundary_forcing",
379
+ "boundary_forcing_with_2d_fill",
380
+ "boundary_forcing_adjusted_for_zeta",
381
+ "boundary_forcing_with_2d_fill_adjusted_for_zeta",
382
+ ],
383
+ )
384
+ def test_boundary_forcing_save(boundary_forcing_fixture, request, tmp_path):
201
385
  """Test save method."""
386
+ boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
202
387
 
203
388
  for file_str in ["test_bf", "test_bf.nc"]:
204
389
  # Create a temporary filepath using the tmp_path fixture
@@ -207,8 +392,8 @@ def test_boundary_forcing_save(boundary_forcing, tmp_path):
207
392
  str(tmp_path / file_str),
208
393
  ]: # test for Path object and str
209
394
 
210
- # Test saving without partitioning and grouping
211
- saved_filenames = boundary_forcing.save(filepath)
395
+ # Test saving without grouping
396
+ saved_filenames = boundary_forcing.save(filepath, group=False)
212
397
 
213
398
  filepath_str = str(Path(filepath).with_suffix(""))
214
399
  expected_filepath = Path(f"{filepath_str}.nc")
@@ -217,28 +402,16 @@ def test_boundary_forcing_save(boundary_forcing, tmp_path):
217
402
  assert expected_filepath.exists()
218
403
  expected_filepath.unlink()
219
404
 
220
- # Test saving without partitioning but with grouping
405
+ # Test saving with grouping
221
406
  saved_filenames = boundary_forcing.save(filepath, group=True)
222
407
 
223
408
  filepath_str = str(Path(filepath).with_suffix(""))
224
- expected_filepath = Path(f"{filepath_str}_202106.nc")
409
+ expected_filepath = Path(f"{filepath_str}_2012.nc")
225
410
 
226
411
  assert saved_filenames == [expected_filepath]
227
412
  assert expected_filepath.exists()
228
413
  expected_filepath.unlink()
229
414
 
230
- # Test saving with partitioning and grouping
231
- saved_filenames = boundary_forcing.save(filepath, np_eta=2, group=True)
232
- expected_filepath_list = [
233
- Path(filepath_str + f"_202106.{index}.nc") for index in range(2)
234
- ]
235
-
236
- assert saved_filenames == expected_filepath_list
237
-
238
- for expected_filepath in expected_filepath_list:
239
- assert expected_filepath.exists()
240
- expected_filepath.unlink()
241
-
242
415
 
243
416
  def test_bgc_boundary_forcing_plot(bgc_boundary_forcing_from_climatology):
244
417
  """Test plot method."""
@@ -264,7 +437,9 @@ def test_bgc_boundary_forcing_save(bgc_boundary_forcing_from_climatology, tmp_pa
264
437
  ]: # test for Path object and str
265
438
 
266
439
  # Test saving without partitioning and grouping
267
- saved_filenames = bgc_boundary_forcing_from_climatology.save(filepath)
440
+ saved_filenames = bgc_boundary_forcing_from_climatology.save(
441
+ filepath, group=False
442
+ )
268
443
 
269
444
  filepath_str = str(Path(filepath).with_suffix(""))
270
445
  expected_filepath = Path(f"{filepath_str}.nc")
@@ -283,20 +458,6 @@ def test_bgc_boundary_forcing_save(bgc_boundary_forcing_from_climatology, tmp_pa
283
458
  assert expected_filepath.exists()
284
459
  expected_filepath.unlink()
285
460
 
286
- # Test saving with partitioning
287
- saved_filenames = bgc_boundary_forcing_from_climatology.save(
288
- filepath, np_xi=2, group=True
289
- )
290
-
291
- expected_filepath_list = [
292
- Path(filepath_str + f"_clim.{index}.nc") for index in range(2)
293
- ]
294
- assert saved_filenames == expected_filepath_list
295
-
296
- for expected_filepath in expected_filepath_list:
297
- assert expected_filepath.exists()
298
- expected_filepath.unlink()
299
-
300
461
 
301
462
  @pytest.mark.parametrize(
302
463
  "bdry_forcing_fixture",
@@ -341,11 +502,12 @@ def test_files_have_same_hash(boundary_forcing, tmp_path, use_dask):
341
502
 
342
503
  filepath_str1 = str(Path(filepath1).with_suffix(""))
343
504
  filepath_str2 = str(Path(filepath2).with_suffix(""))
344
- expected_filepath1 = f"{filepath_str1}_202106.nc"
345
- expected_filepath2 = f"{filepath_str2}_202106.nc"
505
+ expected_filepath1 = f"{filepath_str1}_2012.nc"
506
+ expected_filepath2 = f"{filepath_str2}_2012.nc"
346
507
 
347
- hash1 = calculate_file_hash(expected_filepath1)
348
- hash2 = calculate_file_hash(expected_filepath2)
508
+ # Only compare hash of datasets because metadata is non-deterministic with dask
509
+ hash1 = calculate_data_hash(expected_filepath1)
510
+ hash2 = calculate_data_hash(expected_filepath2)
349
511
 
350
512
  assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
351
513
 
@@ -372,8 +534,9 @@ def test_files_have_same_hash_clim(
372
534
  expected_filepath1 = f"{filepath_str1}_clim.nc"
373
535
  expected_filepath2 = f"{filepath_str2}_clim.nc"
374
536
 
375
- hash1 = calculate_file_hash(expected_filepath1)
376
- hash2 = calculate_file_hash(expected_filepath2)
537
+ # Only compare hash of datasets because metadata is non-deterministic with dask
538
+ hash1 = calculate_data_hash(expected_filepath1)
539
+ hash2 = calculate_data_hash(expected_filepath2)
377
540
 
378
541
  assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
379
542
 
@@ -1,10 +1,12 @@
1
1
  {
2
- "end_time": "2021-06-30 00:00:00",
2
+ "adjust_depth_for_sea_surface_height": "False",
3
+ "apply_2d_horizontal_fill": "False",
4
+ "end_time": "2012-12-31 00:00:00",
3
5
  "hc": 250.0,
4
6
  "model_reference_date": "2000-01-01 00:00:00",
5
- "roms_tools_version": "0.1.dev157+dirty",
7
+ "roms_tools_version": "0.1.dev177",
6
8
  "source": "GLORYS",
7
- "start_time": "2021-06-29 00:00:00",
9
+ "start_time": "2012-01-01 00:00:00",
8
10
  "theta_b": 2.0,
9
11
  "theta_s": 5.0,
10
12
  "title": "ROMS boundary forcing file created by ROMS-Tools"