roms-tools 2.6.2__py3-none-any.whl → 2.7.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 +1 -0
- roms_tools/analysis/roms_output.py +11 -77
- roms_tools/analysis/utils.py +0 -66
- roms_tools/constants.py +2 -0
- roms_tools/download.py +46 -3
- roms_tools/plot.py +22 -5
- roms_tools/setup/cdr_forcing.py +1126 -0
- roms_tools/setup/datasets.py +742 -87
- roms_tools/setup/grid.py +42 -4
- roms_tools/setup/river_forcing.py +11 -84
- roms_tools/setup/tides.py +81 -411
- roms_tools/setup/utils.py +241 -37
- roms_tools/tests/test_setup/test_cdr_forcing.py +772 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +53 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_long_name/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_long_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_long_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_unit/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_unit/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_unit/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +53 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_tracer/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_long_name/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_long_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_long_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_unit/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_unit/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_unit/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/.zattrs +1 -2
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/.zmetadata +27 -5
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ntides/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ntides/.zattrs +5 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ntides/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/omega/.zattrs +1 -3
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/pot_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/pot_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ssh_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ssh_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_datasets.py +103 -1
- roms_tools/tests/test_setup/test_tides.py +112 -47
- roms_tools/utils.py +115 -1
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/METADATA +1 -1
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/RECORD +51 -33
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/WHEEL +1 -1
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from roms_tools import CDRVolumePointSource, Grid
|
|
6
|
+
from roms_tools.constants import NUM_TRACERS
|
|
7
|
+
import xarray as xr
|
|
8
|
+
import numpy as np
|
|
9
|
+
import logging
|
|
10
|
+
from roms_tools.setup.utils import get_tracer_defaults
|
|
11
|
+
from conftest import calculate_file_hash
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import xesmf # type: ignore
|
|
15
|
+
except ImportError:
|
|
16
|
+
xesmf = None
|
|
17
|
+
|
|
18
|
+
# Fixtures
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def iceland_test_grid():
|
|
21
|
+
"""Returns a grid surrouding Iceland."""
|
|
22
|
+
return Grid(
|
|
23
|
+
nx=18, ny=18, size_x=800, size_y=800, center_lon=-18, center_lat=65, rot=0, N=3
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def test_grid_that_straddles():
|
|
29
|
+
"""Returns a grid that straddles the prime meridian."""
|
|
30
|
+
return Grid(
|
|
31
|
+
nx=18, ny=18, size_x=800, size_y=800, center_lon=0, center_lat=65, rot=0, N=3
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def start_end_times():
|
|
37
|
+
"""Returns test start and end times."""
|
|
38
|
+
start_time = datetime(2022, 1, 1)
|
|
39
|
+
end_time = datetime(2022, 12, 31)
|
|
40
|
+
return start_time, end_time
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def empty_cdr_point_source_without_grid(start_end_times):
|
|
45
|
+
"""Returns an empty CDR point source without a grid."""
|
|
46
|
+
start_time, end_time = start_end_times
|
|
47
|
+
return CDRVolumePointSource(
|
|
48
|
+
start_time=start_time,
|
|
49
|
+
end_time=end_time,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def empty_cdr_point_source_with_grid(iceland_test_grid, start_end_times):
|
|
55
|
+
"""Returns an empty CDR point source with the Iceland test grid."""
|
|
56
|
+
start_time, end_time = start_end_times
|
|
57
|
+
return CDRVolumePointSource(
|
|
58
|
+
grid=iceland_test_grid,
|
|
59
|
+
start_time=start_time,
|
|
60
|
+
end_time=end_time,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def empty_cdr_point_source_with_grid_that_straddles(
|
|
66
|
+
test_grid_that_straddles, start_end_times
|
|
67
|
+
):
|
|
68
|
+
"""Returns an empty CDR point source with a grid straddling the prime meridian."""
|
|
69
|
+
start_time, end_time = start_end_times
|
|
70
|
+
return CDRVolumePointSource(
|
|
71
|
+
grid=test_grid_that_straddles,
|
|
72
|
+
start_time=start_time,
|
|
73
|
+
end_time=end_time,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
def valid_release_params():
|
|
79
|
+
"""Returns a dictionary with valid parameters for a CDR point source release within
|
|
80
|
+
the Iceland test domain."""
|
|
81
|
+
return {
|
|
82
|
+
"lat": 66.0,
|
|
83
|
+
"lon": -25.0,
|
|
84
|
+
"depth": 50.0,
|
|
85
|
+
"volume_fluxes": 100.0,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.fixture
|
|
90
|
+
def cdr_point_source_with_two_releases(
|
|
91
|
+
iceland_test_grid, start_end_times, valid_release_params
|
|
92
|
+
):
|
|
93
|
+
"""Returns a CDR point source with one release."""
|
|
94
|
+
start_time, end_time = start_end_times
|
|
95
|
+
cdr = CDRVolumePointSource(
|
|
96
|
+
grid=iceland_test_grid, start_time=start_time, end_time=end_time
|
|
97
|
+
)
|
|
98
|
+
cdr.add_release(name="release1", **valid_release_params)
|
|
99
|
+
|
|
100
|
+
release_params = deepcopy(valid_release_params)
|
|
101
|
+
release_params["times"] = [
|
|
102
|
+
datetime(2022, 1, 1),
|
|
103
|
+
datetime(2022, 1, 3),
|
|
104
|
+
datetime(2022, 1, 5),
|
|
105
|
+
]
|
|
106
|
+
release_params["volume_fluxes"] = [1.0, 2.0, 3.0]
|
|
107
|
+
release_params["tracer_concentrations"] = {"DIC": [10.0, 20.0, 30.0]}
|
|
108
|
+
cdr.add_release(name="release2", **release_params)
|
|
109
|
+
|
|
110
|
+
return cdr
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# Tests
|
|
114
|
+
@pytest.mark.parametrize(
|
|
115
|
+
"cdr_forcing_fixture",
|
|
116
|
+
[
|
|
117
|
+
"empty_cdr_point_source_without_grid",
|
|
118
|
+
"empty_cdr_point_source_with_grid",
|
|
119
|
+
],
|
|
120
|
+
)
|
|
121
|
+
def test_cdr_point_source_init(cdr_forcing_fixture, start_end_times, request):
|
|
122
|
+
"""Tests the initialization of CDR point source fixtures."""
|
|
123
|
+
cdr = request.getfixturevalue(cdr_forcing_fixture)
|
|
124
|
+
start_time, end_time = start_end_times
|
|
125
|
+
assert cdr.start_time == start_time
|
|
126
|
+
assert cdr.end_time == end_time
|
|
127
|
+
|
|
128
|
+
# Check that dataset is empty but has right dimensions and variables
|
|
129
|
+
assert isinstance(cdr.ds, xr.Dataset)
|
|
130
|
+
|
|
131
|
+
# Check dimension lengths
|
|
132
|
+
assert cdr.ds.time.size == 0
|
|
133
|
+
assert cdr.ds.ncdr.size == 0
|
|
134
|
+
assert cdr.ds.ntracers.size == NUM_TRACERS
|
|
135
|
+
|
|
136
|
+
# Check coordinate and variable lengths
|
|
137
|
+
assert cdr.ds.release_name.size == 0
|
|
138
|
+
assert cdr.ds.tracer_name.size == NUM_TRACERS
|
|
139
|
+
assert cdr.ds.tracer_unit.size == NUM_TRACERS
|
|
140
|
+
assert cdr.ds.tracer_long_name.size == NUM_TRACERS
|
|
141
|
+
assert cdr.ds.cdr_time.size == 0
|
|
142
|
+
assert cdr.ds.cdr_lon.size == 0
|
|
143
|
+
assert cdr.ds.cdr_lat.size == 0
|
|
144
|
+
assert cdr.ds.cdr_dep.size == 0
|
|
145
|
+
assert cdr.ds.cdr_hsc.size == 0
|
|
146
|
+
assert cdr.ds.cdr_vsc.size == 0
|
|
147
|
+
assert cdr.ds.cdr_volume.size == 0
|
|
148
|
+
assert cdr.ds.cdr_tracer.size == 0
|
|
149
|
+
|
|
150
|
+
# Check that release dictionary is empty except for tracer metadata
|
|
151
|
+
assert set(cdr.releases.keys()) == {"_tracer_metadata"}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.parametrize(
|
|
155
|
+
"cdr_forcing_fixture",
|
|
156
|
+
[
|
|
157
|
+
"empty_cdr_point_source_without_grid",
|
|
158
|
+
"empty_cdr_point_source_with_grid",
|
|
159
|
+
],
|
|
160
|
+
)
|
|
161
|
+
def test_add_release(cdr_forcing_fixture, valid_release_params, request):
|
|
162
|
+
"""Test some basic features of the `add_release` method for updating forcing dataset
|
|
163
|
+
and dictionary."""
|
|
164
|
+
|
|
165
|
+
cdr = request.getfixturevalue(cdr_forcing_fixture)
|
|
166
|
+
release_params = deepcopy(valid_release_params)
|
|
167
|
+
times = [
|
|
168
|
+
cdr.start_time,
|
|
169
|
+
datetime(2022, 1, 5),
|
|
170
|
+
cdr.end_time,
|
|
171
|
+
]
|
|
172
|
+
release_params["times"] = times
|
|
173
|
+
|
|
174
|
+
volume_fluxes = [100.0, 200.0, 150.0]
|
|
175
|
+
release_params["volume_fluxes"] = volume_fluxes
|
|
176
|
+
|
|
177
|
+
tracer_concentrations = {
|
|
178
|
+
"ALK": [2300.0, 2350.0, 2400.0],
|
|
179
|
+
"DIC": [2100.0, 2150.0, 2200.0],
|
|
180
|
+
"temp": 10.0,
|
|
181
|
+
"salt": 35.0,
|
|
182
|
+
}
|
|
183
|
+
release_params["tracer_concentrations"] = tracer_concentrations
|
|
184
|
+
|
|
185
|
+
# Add release
|
|
186
|
+
cdr.add_release(name="release", **release_params)
|
|
187
|
+
|
|
188
|
+
# Check dimension lengths
|
|
189
|
+
assert cdr.ds.time.size == len(times)
|
|
190
|
+
assert cdr.ds.ncdr.size == 1
|
|
191
|
+
assert cdr.ds.ntracers.size == NUM_TRACERS
|
|
192
|
+
|
|
193
|
+
# Check coordinate and variable lengths
|
|
194
|
+
assert cdr.ds.release_name.size == 1
|
|
195
|
+
assert "release" in cdr.ds["release_name"].values
|
|
196
|
+
assert cdr.ds.tracer_name.size == NUM_TRACERS
|
|
197
|
+
assert cdr.ds.tracer_unit.size == NUM_TRACERS
|
|
198
|
+
assert cdr.ds.tracer_long_name.size == NUM_TRACERS
|
|
199
|
+
assert cdr.ds.cdr_time.size == len(times)
|
|
200
|
+
assert cdr.ds.cdr_lon.size == 1
|
|
201
|
+
assert cdr.ds.cdr_lat.size == 1
|
|
202
|
+
assert cdr.ds.cdr_dep.size == 1
|
|
203
|
+
assert cdr.ds.cdr_hsc.size == 1
|
|
204
|
+
assert cdr.ds.cdr_vsc.size == 1
|
|
205
|
+
|
|
206
|
+
# Check cdr_volume shape and values
|
|
207
|
+
assert cdr.ds.cdr_volume.shape == (len(times), 1)
|
|
208
|
+
np.testing.assert_allclose(cdr.ds.cdr_volume[:, 0], volume_fluxes, rtol=1e-3)
|
|
209
|
+
|
|
210
|
+
# Check tracer concentration shape
|
|
211
|
+
assert cdr.ds.cdr_tracer.shape == (len(times), NUM_TRACERS, 1)
|
|
212
|
+
|
|
213
|
+
# Check tracer concentration values for known tracers
|
|
214
|
+
tracer_index = {name: i for i, name in enumerate(cdr.ds.tracer_name.values)}
|
|
215
|
+
for tracer, expected in tracer_concentrations.items():
|
|
216
|
+
i = tracer_index[tracer]
|
|
217
|
+
if isinstance(expected, list):
|
|
218
|
+
np.testing.assert_allclose(cdr.ds.cdr_tracer[:, i, 0], expected)
|
|
219
|
+
else:
|
|
220
|
+
np.testing.assert_allclose(cdr.ds.cdr_tracer[:, i, 0], expected)
|
|
221
|
+
|
|
222
|
+
assert "release" in cdr.releases.keys()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_merge_multiple_releases(start_end_times, valid_release_params):
|
|
226
|
+
"""Test merging multiple releases in the dataset, including endpoint filling,
|
|
227
|
+
timestamp adjustment, and interpolation."""
|
|
228
|
+
|
|
229
|
+
start_time, end_time = start_end_times
|
|
230
|
+
cdr = CDRVolumePointSource(start_time=start_time, end_time=end_time)
|
|
231
|
+
dic_index = 9
|
|
232
|
+
|
|
233
|
+
# add first release
|
|
234
|
+
release_params1 = deepcopy(valid_release_params)
|
|
235
|
+
release_params1["times"] = [
|
|
236
|
+
datetime(2022, 1, 1), # overall start time
|
|
237
|
+
datetime(2022, 1, 3),
|
|
238
|
+
datetime(2022, 1, 5),
|
|
239
|
+
]
|
|
240
|
+
release_params1["volume_fluxes"] = [1.0, 2.0, 3.0]
|
|
241
|
+
release_params1["tracer_concentrations"] = {"DIC": [10.0, 20.0, 30.0]}
|
|
242
|
+
cdr.add_release(name="release1", **release_params1)
|
|
243
|
+
|
|
244
|
+
# check time
|
|
245
|
+
expected_times = [
|
|
246
|
+
datetime(2022, 1, 1), # overall start time
|
|
247
|
+
datetime(2022, 1, 3),
|
|
248
|
+
datetime(2022, 1, 5),
|
|
249
|
+
datetime(2022, 12, 31), # overall end time
|
|
250
|
+
]
|
|
251
|
+
assert np.array_equal(
|
|
252
|
+
cdr.ds["time"].values, np.array(expected_times, dtype="datetime64[ns]")
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# check first release
|
|
256
|
+
ncdr_index = 0
|
|
257
|
+
|
|
258
|
+
assert cdr.ds["cdr_lon"].isel(ncdr=ncdr_index).values == release_params1["lon"]
|
|
259
|
+
assert cdr.ds["cdr_lat"].isel(ncdr=ncdr_index).values == release_params1["lat"]
|
|
260
|
+
assert cdr.ds["cdr_dep"].isel(ncdr=ncdr_index).values == release_params1["depth"]
|
|
261
|
+
assert cdr.ds["cdr_hsc"].isel(ncdr=ncdr_index).values == 0.0
|
|
262
|
+
assert cdr.ds["cdr_vsc"].isel(ncdr=ncdr_index).values == 0.0
|
|
263
|
+
|
|
264
|
+
expected_volume_fluxes = [
|
|
265
|
+
1.0,
|
|
266
|
+
2.0,
|
|
267
|
+
3.0,
|
|
268
|
+
0.0, # volume flux set to zero at endpoint
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
assert np.allclose(
|
|
272
|
+
cdr.ds["cdr_volume"].isel(ncdr=ncdr_index).values,
|
|
273
|
+
np.array(expected_volume_fluxes),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
expected_dics = [
|
|
277
|
+
10.0,
|
|
278
|
+
20.0,
|
|
279
|
+
30.0,
|
|
280
|
+
30.0, # tracer concenctration extrapolated to endpoint
|
|
281
|
+
]
|
|
282
|
+
assert np.allclose(
|
|
283
|
+
cdr.ds["cdr_tracer"].isel(ncdr=ncdr_index, ntracers=dic_index).values,
|
|
284
|
+
np.array(expected_dics),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# add second release
|
|
288
|
+
release_params2 = deepcopy(valid_release_params)
|
|
289
|
+
release_params2["lon"] = release_params2["lon"] - 1
|
|
290
|
+
release_params2["lat"] = release_params2["lat"] - 1
|
|
291
|
+
release_params2["depth"] = release_params2["depth"] - 1
|
|
292
|
+
|
|
293
|
+
release_params2["times"] = [
|
|
294
|
+
datetime(2022, 1, 2),
|
|
295
|
+
datetime(2022, 1, 4),
|
|
296
|
+
datetime(2022, 1, 5),
|
|
297
|
+
]
|
|
298
|
+
release_params2["volume_fluxes"] = [2.0, 4.0, 10.0]
|
|
299
|
+
release_params2["tracer_concentrations"] = {"DIC": [20.0, 40.0, 100.0]}
|
|
300
|
+
cdr.add_release(name="release2", **release_params2)
|
|
301
|
+
|
|
302
|
+
# check time again
|
|
303
|
+
expected_times = [
|
|
304
|
+
datetime(2022, 1, 1), # overall start time
|
|
305
|
+
datetime(2022, 1, 2),
|
|
306
|
+
datetime(2022, 1, 3),
|
|
307
|
+
datetime(2022, 1, 4),
|
|
308
|
+
datetime(2022, 1, 5),
|
|
309
|
+
datetime(2022, 12, 31), # overall end time
|
|
310
|
+
]
|
|
311
|
+
assert np.array_equal(
|
|
312
|
+
cdr.ds["time"].values, np.array(expected_times, dtype="datetime64[ns]")
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# check first release again
|
|
316
|
+
ncdr_index = 0
|
|
317
|
+
|
|
318
|
+
assert cdr.ds["cdr_lon"].isel(ncdr=ncdr_index).values == release_params1["lon"]
|
|
319
|
+
assert cdr.ds["cdr_lat"].isel(ncdr=ncdr_index).values == release_params1["lat"]
|
|
320
|
+
assert cdr.ds["cdr_dep"].isel(ncdr=ncdr_index).values == release_params1["depth"]
|
|
321
|
+
assert cdr.ds["cdr_hsc"].isel(ncdr=ncdr_index).values == 0.0
|
|
322
|
+
assert cdr.ds["cdr_vsc"].isel(ncdr=ncdr_index).values == 0.0
|
|
323
|
+
|
|
324
|
+
expected_volume_fluxes = [
|
|
325
|
+
1.0,
|
|
326
|
+
1.5,
|
|
327
|
+
2.0,
|
|
328
|
+
2.5,
|
|
329
|
+
3.0,
|
|
330
|
+
0.0, # volume flux set to zero at endpoint
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
assert np.allclose(
|
|
334
|
+
cdr.ds["cdr_volume"].isel(ncdr=ncdr_index).values,
|
|
335
|
+
np.array(expected_volume_fluxes),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
expected_dics = [
|
|
339
|
+
10.0,
|
|
340
|
+
15.0,
|
|
341
|
+
20.0,
|
|
342
|
+
25.0,
|
|
343
|
+
30.0,
|
|
344
|
+
30.0, # tracer concenctration extrapolated to endpoint
|
|
345
|
+
]
|
|
346
|
+
assert np.allclose(
|
|
347
|
+
cdr.ds["cdr_tracer"].isel(ncdr=ncdr_index, ntracers=dic_index).values,
|
|
348
|
+
np.array(expected_dics),
|
|
349
|
+
)
|
|
350
|
+
# check second release
|
|
351
|
+
ncdr_index = 1
|
|
352
|
+
|
|
353
|
+
assert cdr.ds["cdr_lon"].isel(ncdr=ncdr_index).values == release_params2["lon"]
|
|
354
|
+
assert cdr.ds["cdr_lat"].isel(ncdr=ncdr_index).values == release_params2["lat"]
|
|
355
|
+
assert cdr.ds["cdr_dep"].isel(ncdr=ncdr_index).values == release_params2["depth"]
|
|
356
|
+
assert cdr.ds["cdr_hsc"].isel(ncdr=ncdr_index).values == 0.0
|
|
357
|
+
assert cdr.ds["cdr_vsc"].isel(ncdr=ncdr_index).values == 0.0
|
|
358
|
+
|
|
359
|
+
expected_volume_fluxes = [
|
|
360
|
+
0.0, # volume flux set to zero at startpoint
|
|
361
|
+
2.0,
|
|
362
|
+
3.0,
|
|
363
|
+
4.0,
|
|
364
|
+
10.0,
|
|
365
|
+
0.0, # volume flux set to zero at endpoint
|
|
366
|
+
]
|
|
367
|
+
assert np.allclose(
|
|
368
|
+
cdr.ds["cdr_volume"].isel(ncdr=ncdr_index).values,
|
|
369
|
+
np.array(expected_volume_fluxes),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
expected_dics = [
|
|
373
|
+
20.0, # tracer concenctration extrapolated to startpoint
|
|
374
|
+
20.0,
|
|
375
|
+
30.0,
|
|
376
|
+
40.0,
|
|
377
|
+
100.0,
|
|
378
|
+
100.0, # tracer concenctration extrapolated to endpoint
|
|
379
|
+
]
|
|
380
|
+
assert np.allclose(
|
|
381
|
+
cdr.ds["cdr_tracer"].isel(ncdr=ncdr_index, ntracers=dic_index).values,
|
|
382
|
+
np.array(expected_dics),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def test_cdr_point_source_init_invalid_times():
|
|
387
|
+
"""Test that initializing a CDR point source with the same start and end time raises
|
|
388
|
+
a ValueError."""
|
|
389
|
+
start_time = datetime(2022, 5, 1)
|
|
390
|
+
end_time = datetime(2022, 5, 1)
|
|
391
|
+
with pytest.raises(
|
|
392
|
+
ValueError, match="`start_time` must be earlier than `end_time`"
|
|
393
|
+
):
|
|
394
|
+
CDRVolumePointSource(start_time=start_time, end_time=end_time)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@pytest.mark.parametrize(
|
|
398
|
+
"cdr_forcing_fixture",
|
|
399
|
+
[
|
|
400
|
+
"empty_cdr_point_source_without_grid",
|
|
401
|
+
"empty_cdr_point_source_with_grid",
|
|
402
|
+
],
|
|
403
|
+
)
|
|
404
|
+
def test_add_duplicate_release(cdr_forcing_fixture, valid_release_params, request):
|
|
405
|
+
"""Test that adding a duplicate release raises a ValueError."""
|
|
406
|
+
cdr = request.getfixturevalue(cdr_forcing_fixture)
|
|
407
|
+
release_params = deepcopy(valid_release_params)
|
|
408
|
+
cdr.add_release(name="release_1", **release_params)
|
|
409
|
+
with pytest.raises(
|
|
410
|
+
ValueError, match="A release with the name 'release_1' already exists."
|
|
411
|
+
):
|
|
412
|
+
cdr.add_release(name="release_1", **release_params)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@pytest.mark.parametrize(
|
|
416
|
+
"cdr_forcing_fixture",
|
|
417
|
+
[
|
|
418
|
+
"empty_cdr_point_source_without_grid",
|
|
419
|
+
"empty_cdr_point_source_with_grid",
|
|
420
|
+
],
|
|
421
|
+
)
|
|
422
|
+
def test_invalid_release_params(cdr_forcing_fixture, valid_release_params, request):
|
|
423
|
+
"""Test that invalid release parameters raise the appropriate ValueErrors."""
|
|
424
|
+
cdr = request.getfixturevalue(cdr_forcing_fixture)
|
|
425
|
+
|
|
426
|
+
# Test invalid latitude
|
|
427
|
+
invalid_params = deepcopy(valid_release_params)
|
|
428
|
+
invalid_params["lat"] = 100.0
|
|
429
|
+
with pytest.raises(ValueError, match="Latitude must be between -90 and 90."):
|
|
430
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
431
|
+
|
|
432
|
+
# Test invalid depth (negative value)
|
|
433
|
+
invalid_params = deepcopy(valid_release_params)
|
|
434
|
+
invalid_params["depth"] = -10.0
|
|
435
|
+
with pytest.raises(ValueError, match="Depth must be a non-negative number."):
|
|
436
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
437
|
+
|
|
438
|
+
# Test times not being datetime objects
|
|
439
|
+
invalid_params = deepcopy(valid_release_params)
|
|
440
|
+
invalid_params["times"] = [
|
|
441
|
+
"2023-01-01",
|
|
442
|
+
"2023-02-01",
|
|
443
|
+
] # Invalid times format (strings)
|
|
444
|
+
with pytest.raises(
|
|
445
|
+
ValueError,
|
|
446
|
+
match="If 'times' is provided, all entries must be datetime objects.",
|
|
447
|
+
):
|
|
448
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
449
|
+
|
|
450
|
+
# Test times being not monotonically increasing
|
|
451
|
+
invalid_params = deepcopy(valid_release_params)
|
|
452
|
+
invalid_params["times"] = [
|
|
453
|
+
datetime(2022, 1, 1),
|
|
454
|
+
datetime(2022, 2, 1),
|
|
455
|
+
datetime(2022, 1, 15), # Out of order date
|
|
456
|
+
datetime(2022, 3, 1),
|
|
457
|
+
]
|
|
458
|
+
with pytest.raises(
|
|
459
|
+
ValueError, match="The 'times' list must be strictly monotonically increasing."
|
|
460
|
+
):
|
|
461
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
462
|
+
|
|
463
|
+
# Test times being not strictly monotonically increasing
|
|
464
|
+
invalid_params = deepcopy(valid_release_params)
|
|
465
|
+
invalid_params["times"] = [
|
|
466
|
+
datetime(2022, 1, 1),
|
|
467
|
+
datetime(2022, 2, 1),
|
|
468
|
+
datetime(2022, 2, 1), # Duplicated time
|
|
469
|
+
datetime(2022, 3, 1),
|
|
470
|
+
]
|
|
471
|
+
with pytest.raises(
|
|
472
|
+
ValueError, match="The 'times' list must be strictly monotonically increasing."
|
|
473
|
+
):
|
|
474
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
475
|
+
|
|
476
|
+
# Test first time earlier than self.start_time
|
|
477
|
+
invalid_params = deepcopy(valid_release_params)
|
|
478
|
+
invalid_params["times"] = [
|
|
479
|
+
datetime(2000, 1, 1),
|
|
480
|
+
datetime(2022, 2, 1),
|
|
481
|
+
] # Earlier than self.start_time
|
|
482
|
+
with pytest.raises(ValueError, match="First entry"):
|
|
483
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
484
|
+
|
|
485
|
+
# Test last time later than self.end_time
|
|
486
|
+
invalid_params = deepcopy(valid_release_params)
|
|
487
|
+
invalid_params["times"] = [
|
|
488
|
+
datetime(2022, 1, 1),
|
|
489
|
+
datetime(2025, 1, 1),
|
|
490
|
+
] # Later than self.end_time
|
|
491
|
+
with pytest.raises(ValueError, match="Last entry"):
|
|
492
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
493
|
+
|
|
494
|
+
# Test invalid volume_fluxes: not a float/int or list of float/int
|
|
495
|
+
invalid_params = deepcopy(valid_release_params)
|
|
496
|
+
invalid_params["volume_fluxes"] = ["not", "valid"]
|
|
497
|
+
with pytest.raises(ValueError, match="Invalid 'volume_fluxes' input"):
|
|
498
|
+
cdr.add_release(name="release_invalid_volume", **invalid_params)
|
|
499
|
+
|
|
500
|
+
# Test invalid tracer_concentrations: not a float/int or list of float/int
|
|
501
|
+
invalid_params = deepcopy(valid_release_params)
|
|
502
|
+
invalid_params["tracer_concentrations"] = {"ALK": ["not", "valid"]}
|
|
503
|
+
with pytest.raises(ValueError, match="Invalid tracer concentration for 'ALK'"):
|
|
504
|
+
cdr.add_release(name="release_invalid_tracer", **invalid_params)
|
|
505
|
+
|
|
506
|
+
# Test mismatch between times and volume fluxes length
|
|
507
|
+
invalid_params = deepcopy(valid_release_params)
|
|
508
|
+
invalid_params["times"] = [datetime(2022, 1, 1), datetime(2022, 1, 2)] # Two times
|
|
509
|
+
invalid_params["volume_fluxes"] = [100] # Only one volume flux entry
|
|
510
|
+
with pytest.raises(ValueError, match="The length of `volume_fluxes` "):
|
|
511
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
512
|
+
|
|
513
|
+
# Test mismatch between times and tracer_concentrations length
|
|
514
|
+
invalid_params = deepcopy(valid_release_params)
|
|
515
|
+
invalid_params["times"] = [datetime(2022, 1, 1), datetime(2022, 1, 2)] # Two times
|
|
516
|
+
invalid_params["tracer_concentrations"] = {
|
|
517
|
+
"ALK": [1]
|
|
518
|
+
} # Only one tracer concentration
|
|
519
|
+
with pytest.raises(ValueError, match="The length of tracer 'ALK'"):
|
|
520
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
521
|
+
|
|
522
|
+
# Test invalid volume flux (negative)
|
|
523
|
+
invalid_params = deepcopy(valid_release_params)
|
|
524
|
+
invalid_params["volume_fluxes"] = -100 # Invalid volume flux
|
|
525
|
+
with pytest.raises(ValueError, match="Volume flux must be non-negative"):
|
|
526
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
527
|
+
|
|
528
|
+
# Test volume flux as list with negative values
|
|
529
|
+
invalid_params = deepcopy(valid_release_params)
|
|
530
|
+
invalid_params["times"] = [cdr.start_time, cdr.end_time]
|
|
531
|
+
invalid_params["volume_fluxes"] = [10, -5] # Invalid volume fluxes in list
|
|
532
|
+
with pytest.raises(
|
|
533
|
+
ValueError, match="All entries in `volume_fluxes` must be non-negative"
|
|
534
|
+
):
|
|
535
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
536
|
+
|
|
537
|
+
# Test invalid tracer concentration (negative)
|
|
538
|
+
invalid_params = deepcopy(valid_release_params)
|
|
539
|
+
invalid_params["tracer_concentrations"] = {"ALK": -1}
|
|
540
|
+
with pytest.raises(ValueError, match="The concentration of tracer"):
|
|
541
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
542
|
+
|
|
543
|
+
# Test tracer_concentration as list with negative values
|
|
544
|
+
invalid_params = deepcopy(valid_release_params)
|
|
545
|
+
invalid_params["times"] = [cdr.start_time, cdr.end_time]
|
|
546
|
+
invalid_params["tracer_concentrations"] = {
|
|
547
|
+
"ALK": [10, -5]
|
|
548
|
+
} # Invalid concentration in list
|
|
549
|
+
with pytest.raises(ValueError, match="All entries in "):
|
|
550
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def test_warning_no_grid(
|
|
554
|
+
empty_cdr_point_source_without_grid, valid_release_params, caplog
|
|
555
|
+
):
|
|
556
|
+
"""Test warning if no grid is provided."""
|
|
557
|
+
with caplog.at_level(logging.WARNING):
|
|
558
|
+
empty_cdr_point_source_without_grid.add_release(
|
|
559
|
+
name="release_1", **valid_release_params
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
assert "Grid not provided"
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@pytest.mark.parametrize(
|
|
566
|
+
"cdr_forcing_fixture",
|
|
567
|
+
[
|
|
568
|
+
"empty_cdr_point_source_with_grid",
|
|
569
|
+
"empty_cdr_point_source_with_grid_that_straddles",
|
|
570
|
+
],
|
|
571
|
+
)
|
|
572
|
+
def test_invalid_release_longitude(cdr_forcing_fixture, valid_release_params, request):
|
|
573
|
+
"""Test that error is raised if release location is outside grid."""
|
|
574
|
+
|
|
575
|
+
cdr = request.getfixturevalue(cdr_forcing_fixture)
|
|
576
|
+
|
|
577
|
+
# Release location outside of domain
|
|
578
|
+
invalid_params = deepcopy(valid_release_params)
|
|
579
|
+
invalid_params["lon"] = -30
|
|
580
|
+
invalid_params["lat"] = 60
|
|
581
|
+
with pytest.raises(ValueError, match="outside of the grid domain"):
|
|
582
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
583
|
+
|
|
584
|
+
# Release location outside of domain
|
|
585
|
+
invalid_params = deepcopy(valid_release_params)
|
|
586
|
+
invalid_params["lon"] = 360 - 30
|
|
587
|
+
invalid_params["lat"] = 60
|
|
588
|
+
with pytest.raises(ValueError, match="outside of the grid domain"):
|
|
589
|
+
cdr.add_release(name="release_1", **invalid_params)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def test_invalid_release_location(
|
|
593
|
+
empty_cdr_point_source_with_grid, valid_release_params
|
|
594
|
+
):
|
|
595
|
+
"""Test that error is raised if release location is outside grid or on land."""
|
|
596
|
+
# Release location too close to boundary of Iceland domain; lat_rho[0, 0] = 60.97, lon_rho[0, 0] = 334.17
|
|
597
|
+
invalid_params = deepcopy(valid_release_params)
|
|
598
|
+
invalid_params["lon"] = 334.17
|
|
599
|
+
invalid_params["lat"] = 60.97
|
|
600
|
+
with pytest.raises(ValueError, match="too close to the grid boundary"):
|
|
601
|
+
empty_cdr_point_source_with_grid.add_release(name="release_1", **invalid_params)
|
|
602
|
+
|
|
603
|
+
# Release location lies on land
|
|
604
|
+
invalid_params = deepcopy(valid_release_params)
|
|
605
|
+
invalid_params["lon"] = -20.0
|
|
606
|
+
invalid_params["lat"] = 64.5
|
|
607
|
+
with pytest.raises(ValueError, match="on land"):
|
|
608
|
+
empty_cdr_point_source_with_grid.add_release(name="release_1", **invalid_params)
|
|
609
|
+
|
|
610
|
+
# Release location lies below seafloor
|
|
611
|
+
invalid_params = deepcopy(valid_release_params)
|
|
612
|
+
invalid_params["depth"] = 4000
|
|
613
|
+
with pytest.raises(ValueError, match="below the seafloor"):
|
|
614
|
+
empty_cdr_point_source_with_grid.add_release(name="release_1", **invalid_params)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def test_add_release_tracer_zero_fill(start_end_times, valid_release_params):
|
|
618
|
+
"""Test that zero fill of tracer concentrations works as expected."""
|
|
619
|
+
start_time, end_time = start_end_times
|
|
620
|
+
cdr = CDRVolumePointSource(start_time=start_time, end_time=end_time)
|
|
621
|
+
release_params = deepcopy(valid_release_params)
|
|
622
|
+
release_params["fill_values"] = "zero"
|
|
623
|
+
cdr.add_release(name="filled_release", **release_params)
|
|
624
|
+
defaults = get_tracer_defaults()
|
|
625
|
+
# temp
|
|
626
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=0) == defaults["temp"]).all()
|
|
627
|
+
# salt
|
|
628
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=1) == defaults["salt"]).all()
|
|
629
|
+
# all other tracers should be zero
|
|
630
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=slice(2, None)) == 0.0).all()
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def test_add_release_tracer_auto_fill(start_end_times, valid_release_params):
|
|
634
|
+
"""Test that auto fill of tracer concentrations works as expected."""
|
|
635
|
+
start_time, end_time = start_end_times
|
|
636
|
+
# Check that the tracer concentrations are auto-filled where missing
|
|
637
|
+
cdr = CDRVolumePointSource(start_time=start_time, end_time=end_time)
|
|
638
|
+
release_params = deepcopy(valid_release_params)
|
|
639
|
+
release_params["fill_values"] = "auto"
|
|
640
|
+
cdr.add_release(name="filled_release", **release_params)
|
|
641
|
+
|
|
642
|
+
defaults = get_tracer_defaults()
|
|
643
|
+
# temp
|
|
644
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=0) == defaults["temp"]).all()
|
|
645
|
+
# salt
|
|
646
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=1) == defaults["salt"]).all()
|
|
647
|
+
# ALK
|
|
648
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=11) == defaults["ALK"]).all()
|
|
649
|
+
# all other tracers should also be equal to the tracer default values, so not equal to zero
|
|
650
|
+
assert (cdr.ds["cdr_tracer"].isel(ntracers=slice(2, None)) > 0.0).all()
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def test_add_release_invalid_fill(start_end_times, valid_release_params):
|
|
654
|
+
"""Test that invalid fill method of tracer concentrations raises error."""
|
|
655
|
+
start_time, end_time = start_end_times
|
|
656
|
+
cdr = CDRVolumePointSource(start_time=start_time, end_time=end_time)
|
|
657
|
+
release_params = deepcopy(valid_release_params)
|
|
658
|
+
release_params["fill_values"] = "zero_fill"
|
|
659
|
+
|
|
660
|
+
with pytest.raises(ValueError, match="Invalid fill_values option"):
|
|
661
|
+
|
|
662
|
+
cdr.add_release(name="filled_release", **release_params)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def test_plot_error_when_no_grid(start_end_times, valid_release_params):
|
|
666
|
+
"""Test that error is raised if plotting without a grid."""
|
|
667
|
+
start_time, end_time = start_end_times
|
|
668
|
+
cdr = CDRVolumePointSource(start_time=start_time, end_time=end_time)
|
|
669
|
+
release_params = deepcopy(valid_release_params)
|
|
670
|
+
cdr.add_release(name="release1", **release_params)
|
|
671
|
+
|
|
672
|
+
with pytest.raises(ValueError, match="A grid must be provided for plotting"):
|
|
673
|
+
cdr.plot_location_top_view("all")
|
|
674
|
+
|
|
675
|
+
with pytest.raises(ValueError, match="A grid must be provided for plotting"):
|
|
676
|
+
cdr.plot_location_side_view("release1")
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def test_plot(cdr_point_source_with_two_releases):
|
|
680
|
+
"""Test that plotting method run without error."""
|
|
681
|
+
|
|
682
|
+
cdr_point_source_with_two_releases.plot_volume_flux()
|
|
683
|
+
cdr_point_source_with_two_releases.plot_tracer_concentration("ALK")
|
|
684
|
+
cdr_point_source_with_two_releases.plot_tracer_concentration("DIC")
|
|
685
|
+
|
|
686
|
+
cdr_point_source_with_two_releases.plot_location_top_view()
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@pytest.mark.skipif(xesmf is None, reason="xesmf required")
|
|
690
|
+
def test_plot_side_view(cdr_point_source_with_two_releases):
|
|
691
|
+
"""Test that plotting method run without error."""
|
|
692
|
+
|
|
693
|
+
cdr_point_source_with_two_releases.plot_location_side_view("release1")
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def test_plot_more_errors(cdr_point_source_with_two_releases):
|
|
697
|
+
"""Test that error is raised on bad plot args or ambiguous release."""
|
|
698
|
+
|
|
699
|
+
with pytest.raises(ValueError, match="Multiple releases found"):
|
|
700
|
+
cdr_point_source_with_two_releases.plot_location_side_view()
|
|
701
|
+
|
|
702
|
+
with pytest.raises(ValueError, match="Invalid release"):
|
|
703
|
+
cdr_point_source_with_two_releases.plot_location_side_view(release="fake")
|
|
704
|
+
|
|
705
|
+
with pytest.raises(ValueError, match="Invalid releases"):
|
|
706
|
+
cdr_point_source_with_two_releases.plot_location_top_view(releases=["fake"])
|
|
707
|
+
|
|
708
|
+
with pytest.raises(ValueError, match="should be a string"):
|
|
709
|
+
cdr_point_source_with_two_releases.plot_location_top_view(releases=4)
|
|
710
|
+
|
|
711
|
+
with pytest.raises(ValueError, match="list must be strings"):
|
|
712
|
+
cdr_point_source_with_two_releases.plot_location_top_view(releases=[4])
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def test_cdr_forcing_save(cdr_point_source_with_two_releases, tmp_path):
|
|
716
|
+
"""Test save method."""
|
|
717
|
+
|
|
718
|
+
for file_str in ["test_cdr_forcing", "test_cdr_forcing.nc"]:
|
|
719
|
+
# Create a temporary filepath using the tmp_path fixture
|
|
720
|
+
for filepath in [tmp_path / file_str, str(tmp_path / file_str)]:
|
|
721
|
+
|
|
722
|
+
saved_filenames = cdr_point_source_with_two_releases.save(filepath)
|
|
723
|
+
# Check if the .nc file was created
|
|
724
|
+
filepath = Path(filepath).with_suffix(".nc")
|
|
725
|
+
assert saved_filenames == [filepath]
|
|
726
|
+
assert filepath.exists()
|
|
727
|
+
# Clean up the .nc file
|
|
728
|
+
filepath.unlink()
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def test_roundtrip_yaml(cdr_point_source_with_two_releases, tmp_path):
|
|
732
|
+
"""Test that creating a CDRVolumePointSource object, saving its parameters to yaml
|
|
733
|
+
file, and re-opening yaml file creates the same object."""
|
|
734
|
+
|
|
735
|
+
# Create a temporary filepath using the tmp_path fixture
|
|
736
|
+
file_str = "test_yaml"
|
|
737
|
+
for filepath in [
|
|
738
|
+
tmp_path / file_str,
|
|
739
|
+
str(tmp_path / file_str),
|
|
740
|
+
]: # test for Path object and str
|
|
741
|
+
|
|
742
|
+
cdr_point_source_with_two_releases.to_yaml(filepath)
|
|
743
|
+
|
|
744
|
+
cdr_forcing_from_file = CDRVolumePointSource.from_yaml(filepath)
|
|
745
|
+
|
|
746
|
+
assert cdr_point_source_with_two_releases == cdr_forcing_from_file
|
|
747
|
+
|
|
748
|
+
filepath = Path(filepath)
|
|
749
|
+
filepath.unlink()
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def test_files_have_same_hash(cdr_point_source_with_two_releases, tmp_path):
|
|
753
|
+
"""Test that saving the same CDR forcing configuration to NetCDF twice results in
|
|
754
|
+
reproducible file hashes."""
|
|
755
|
+
|
|
756
|
+
yaml_filepath = tmp_path / "test_yaml.yaml"
|
|
757
|
+
filepath1 = tmp_path / "test1.nc"
|
|
758
|
+
filepath2 = tmp_path / "test2.nc"
|
|
759
|
+
|
|
760
|
+
cdr_point_source_with_two_releases.to_yaml(yaml_filepath)
|
|
761
|
+
cdr_point_source_with_two_releases.save(filepath1)
|
|
762
|
+
cdr_from_file = CDRVolumePointSource.from_yaml(yaml_filepath)
|
|
763
|
+
cdr_from_file.save(filepath2)
|
|
764
|
+
|
|
765
|
+
hash1 = calculate_file_hash(filepath1)
|
|
766
|
+
hash2 = calculate_file_hash(filepath2)
|
|
767
|
+
|
|
768
|
+
assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
|
|
769
|
+
|
|
770
|
+
yaml_filepath.unlink()
|
|
771
|
+
filepath1.unlink()
|
|
772
|
+
filepath2.unlink()
|