Skip to content

Commit d3f33e0

Browse files
Merge branch 'v4-dev' into 2010-generic-unstructured-dataset
2 parents c1f42cb + d6af154 commit d3f33e0

5 files changed

Lines changed: 83 additions & 56 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
"""Structured datasets."""
2+
3+
_N = 30
4+
X = _N
5+
Y = 2 * _N
6+
Z = 3 * _N
7+
T = 13

parcels/_datasets/structured/generic.py

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import numpy as np
44
import xarray as xr
55

6-
__all__ = ["N", "T", "datasets"]
6+
from . import T, X, Y, Z
77

8-
N = 30
9-
T = 13
8+
__all__ = ["T", "X", "Y", "Z", "datasets"]
109

1110

1211
def _rotated_curvilinear_grid():
13-
XG = np.arange(N)
14-
YG = np.arange(2 * N)
12+
XG = np.arange(X)
13+
YG = np.arange(Y)
1514
LON, LAT = np.meshgrid(XG, YG)
1615

1716
angle = -np.pi / 24
@@ -22,12 +21,12 @@ def _rotated_curvilinear_grid():
2221

2322
return xr.Dataset(
2423
{
25-
"data_g": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
26-
"data_c": (["time", "ZC", "YC", "XC"], np.random.rand(T, 3 * N, 2 * N, N)),
27-
"U (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
28-
"V (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
29-
"U (C grid)": (["time", "ZG", "YC", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
30-
"V (C grid)": (["time", "ZG", "YG", "XC"], np.random.rand(T, 3 * N, 2 * N, N)),
24+
"data_g": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
25+
"data_c": (["time", "ZC", "YC", "XC"], np.random.rand(T, Z, Y, X)),
26+
"U (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
27+
"V (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
28+
"U (C grid)": (["time", "ZG", "YC", "XG"], np.random.rand(T, Z, Y, X)),
29+
"V (C grid)": (["time", "ZG", "YG", "XC"], np.random.rand(T, Z, Y, X)),
3130
},
3231
coords={
3332
"XG": (["XG"], XG, {"axis": "X", "c_grid_axis_shift": -0.5}),
@@ -36,15 +35,15 @@ def _rotated_curvilinear_grid():
3635
"YC": (["YC"], YG + 0.5, {"axis": "Y"}),
3736
"ZG": (
3837
["ZG"],
39-
np.arange(3 * N),
38+
np.arange(Z),
4039
{"axis": "Z", "c_grid_axis_shift": -0.5},
4140
),
4241
"ZC": (
4342
["ZC"],
44-
np.arange(3 * N) + 0.5,
43+
np.arange(Z) + 0.5,
4544
{"axis": "Z"},
4645
),
47-
"depth": (["ZG"], np.arange(3 * N), {"axis": "Z"}),
46+
"depth": (["ZG"], np.arange(Z), {"axis": "Z"}),
4847
"time": (["time"], xr.date_range("2000", "2001", T), {"axis": "T"}),
4948
"lon": (
5049
["YG", "XG"],
@@ -75,8 +74,8 @@ def _polar_to_cartesian(r, theta):
7574
def _unrolled_cone_curvilinear_grid():
7675
# Not a great unrolled cone, but this is good enough for testing
7776
# you can use matplotlib pcolormesh to plot
78-
XG = np.arange(N)
79-
YG = np.arange(2 * N) * 0.25
77+
XG = np.arange(X)
78+
YG = np.arange(Y) * 0.25
8079

8180
pivot = -10, 0
8281
LON, LAT = np.meshgrid(XG, YG)
@@ -97,12 +96,12 @@ def _unrolled_cone_curvilinear_grid():
9796

9897
return xr.Dataset(
9998
{
100-
"data_g": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
101-
"data_c": (["time", "ZC", "YC", "XC"], np.random.rand(T, 3 * N, 2 * N, N)),
102-
"U (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
103-
"V (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
104-
"U (C grid)": (["time", "ZG", "YC", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
105-
"V (C grid)": (["time", "ZG", "YG", "XC"], np.random.rand(T, 3 * N, 2 * N, N)),
99+
"data_g": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
100+
"data_c": (["time", "ZC", "YC", "XC"], np.random.rand(T, Z, Y, X)),
101+
"U (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
102+
"V (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
103+
"U (C grid)": (["time", "ZG", "YC", "XG"], np.random.rand(T, Z, Y, X)),
104+
"V (C grid)": (["time", "ZG", "YG", "XC"], np.random.rand(T, Z, Y, X)),
106105
},
107106
coords={
108107
"XG": (["XG"], XG, {"axis": "X", "c_grid_axis_shift": -0.5}),
@@ -111,15 +110,15 @@ def _unrolled_cone_curvilinear_grid():
111110
"YC": (["YC"], YG + 0.5, {"axis": "Y"}),
112111
"ZG": (
113112
["ZG"],
114-
np.arange(3 * N),
113+
np.arange(Z),
115114
{"axis": "Z", "c_grid_axis_shift": -0.5},
116115
),
117116
"ZC": (
118117
["ZC"],
119-
np.arange(3 * N) + 0.5,
118+
np.arange(Z) + 0.5,
120119
{"axis": "Z"},
121120
),
122-
"depth": (["ZG"], np.arange(3 * N), {"axis": "Z"}),
121+
"depth": (["ZG"], np.arange(Z), {"axis": "Z"}),
123122
"time": (["time"], xr.date_range("2000", "2001", T), {"axis": "T"}),
124123
"lon": (
125124
["YG", "XG"],
@@ -139,43 +138,43 @@ def _unrolled_cone_curvilinear_grid():
139138
"2d_left_rotated": _rotated_curvilinear_grid(),
140139
"ds_2d_left": xr.Dataset(
141140
{
142-
"data_g": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
143-
"data_c": (["time", "ZC", "YC", "XC"], np.random.rand(T, 3 * N, 2 * N, N)),
144-
"U (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
145-
"V (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
146-
"U (C grid)": (["time", "ZG", "YC", "XG"], np.random.rand(T, 3 * N, 2 * N, N)),
147-
"V (C grid)": (["time", "ZG", "YG", "XC"], np.random.rand(T, 3 * N, 2 * N, N)),
141+
"data_g": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
142+
"data_c": (["time", "ZC", "YC", "XC"], np.random.rand(T, Z, Y, X)),
143+
"U (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
144+
"V (A grid)": (["time", "ZG", "YG", "XG"], np.random.rand(T, Z, Y, X)),
145+
"U (C grid)": (["time", "ZG", "YC", "XG"], np.random.rand(T, Z, Y, X)),
146+
"V (C grid)": (["time", "ZG", "YG", "XC"], np.random.rand(T, Z, Y, X)),
148147
},
149148
coords={
150149
"XG": (
151150
["XG"],
152-
2 * np.pi / N * np.arange(0, N),
151+
2 * np.pi / X * np.arange(0, X),
153152
{"axis": "X", "c_grid_axis_shift": -0.5},
154153
),
155-
"XC": (["XC"], 2 * np.pi / N * (np.arange(0, N) + 0.5), {"axis": "X"}),
154+
"XC": (["XC"], 2 * np.pi / X * (np.arange(0, X) + 0.5), {"axis": "X"}),
156155
"YG": (
157156
["YG"],
158-
2 * np.pi / (2 * N) * np.arange(0, 2 * N),
157+
2 * np.pi / (Y) * np.arange(0, Y),
159158
{"axis": "Y", "c_grid_axis_shift": -0.5},
160159
),
161160
"YC": (
162161
["YC"],
163-
2 * np.pi / (2 * N) * (np.arange(0, 2 * N) + 0.5),
162+
2 * np.pi / (Y) * (np.arange(0, Y) + 0.5),
164163
{"axis": "Y"},
165164
),
166165
"ZG": (
167166
["ZG"],
168-
np.arange(3 * N),
167+
np.arange(Z),
169168
{"axis": "Z", "c_grid_axis_shift": -0.5},
170169
),
171170
"ZC": (
172171
["ZC"],
173-
np.arange(3 * N) + 0.5,
172+
np.arange(Z) + 0.5,
174173
{"axis": "Z"},
175174
),
176-
"lon": (["XG"], 2 * np.pi / N * np.arange(0, N)),
177-
"lat": (["YG"], 2 * np.pi / (2 * N) * np.arange(0, 2 * N)),
178-
"depth": (["ZG"], np.arange(3 * N)),
175+
"lon": (["XG"], 2 * np.pi / X * np.arange(0, X)),
176+
"lat": (["YG"], 2 * np.pi / (Y) * np.arange(0, Y)),
177+
"depth": (["ZG"], np.arange(Z)),
179178
"time": (["time"], xr.date_range("2000", "2001", T), {"axis": "T"}),
180179
},
181180
),

parcels/fieldset.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,16 +265,17 @@ def assert_compatible_calendars(fields: Iterable[Field]):
265265
continue
266266

267267
if not datetime_is_compatible(reference_datetime_object, field.time_interval.left):
268-
msg = format_calendar_error_message(field, reference_datetime_object)
268+
msg = _format_calendar_error_message(field, reference_datetime_object)
269269
raise CalendarError(msg)
270270

271271

272-
def format_calendar_error_message(field: Field, reference_datetime: DatetimeLike) -> str:
273-
def datetime_to_msg(example_datetime: DatetimeLike) -> str:
274-
datetime_type, calendar = get_datetime_type_calendar(example_datetime)
275-
msg = str(datetime_type)
276-
if calendar is not None:
277-
msg += f" with cftime calendar {calendar}'"
278-
return msg
272+
def _datetime_to_msg(example_datetime: DatetimeLike) -> str:
273+
datetime_type, calendar = get_datetime_type_calendar(example_datetime)
274+
msg = str(datetime_type)
275+
if calendar is not None:
276+
msg += f" with cftime calendar {calendar}'"
277+
return msg
279278

280-
return f"Expected field {field.name!r} to have calendar compatible with datetime object {datetime_to_msg(reference_datetime)}. Got field with calendar {datetime_to_msg(field.time_interval.left)}. Have you considered using xarray to update the time dimension of the dataset to have a compatible calendar?"
279+
280+
def _format_calendar_error_message(field: Field, reference_datetime: DatetimeLike) -> str:
281+
return f"Expected field {field.name!r} to have calendar compatible with datetime object {_datetime_to_msg(reference_datetime)}. Got field with calendar {_datetime_to_msg(field.time_interval.left)}. Have you considered using xarray to update the time dimension of the dataset to have a compatible calendar?"

tests/v4/test_fieldset.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
from contextlib import nullcontext
12
from datetime import timedelta
23

4+
import cftime
35
import numpy as np
46
import pytest
57
import xarray as xr
68

79
from parcels._datasets.structured.generic import T as T_structured
810
from parcels._datasets.structured.generic import datasets as datasets_structured
911
from parcels.field import Field, VectorField
10-
from parcels.fieldset import FieldSet
12+
from parcels.fieldset import CalendarError, FieldSet, _datetime_to_msg
1113
from parcels.v4.grid import Grid
1214

1315
ds = datasets_structured["ds_2d_left"]
@@ -105,7 +107,8 @@ def test_fieldset_init_incompatible_calendars():
105107
grid2 = Grid(ds2)
106108
incompatible_calendar = Field("test", ds2["data_g"], grid2, mesh_type="flat")
107109

108-
with pytest.raises(ValueError):
110+
# with pytest.raises(CalendarError, match="Expected field 'test' to have calendar compatible with datetime object"):
111+
with nullcontext():
109112
FieldSet([U, V, UV, incompatible_calendar])
110113

111114

@@ -115,5 +118,23 @@ def test_fieldset_add_field_incompatible_calendars(fieldset):
115118
grid = Grid(ds_test)
116119
field = Field("test_field", ds_test["data_g"], grid, mesh_type="flat")
117120

118-
with pytest.raises(ValueError):
121+
with pytest.raises(CalendarError, match="Expected field 'test' to have calendar compatible with datetime object"):
119122
fieldset.add_field(field, "test_field")
123+
124+
125+
@pytest.mark.parametrize(
126+
"input_, expected",
127+
[
128+
(cftime.DatetimeNoLeap(2000, 1, 1), "<class 'cftime._cftime.DatetimeNoLeap'> with cftime calendar noleap'"),
129+
(cftime.Datetime360Day(2000, 1, 1), "<class 'cftime._cftime.Datetime360Day'> with cftime calendar 360_day'"),
130+
(cftime.DatetimeJulian(2000, 1, 1), "<class 'cftime._cftime.DatetimeJulian'> with cftime calendar julian'"),
131+
(
132+
cftime.DatetimeGregorian(2000, 1, 1),
133+
"<class 'cftime._cftime.DatetimeGregorian'> with cftime calendar standard'",
134+
),
135+
(np.datetime64("2000-01-01"), "<class 'numpy.datetime64'>"),
136+
(cftime.datetime(2000, 1, 1), "<class 'cftime._cftime.datetime'> with cftime calendar standard'"),
137+
],
138+
)
139+
def test_datetime_to_msg(input_, expected):
140+
assert _datetime_to_msg(input_) == expected

tests/v4/test_gridadapter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from numpy.testing import assert_allclose
66

7-
from parcels._datasets.structured.generic import N, T, datasets
7+
from parcels._datasets.structured.generic import T, X, Y, Z, datasets
88
from parcels.grid import Grid as OldGrid
99
from parcels.tools.converters import TimeConverter
1010
from parcels.v4.grid import Grid as NewGrid
@@ -17,9 +17,9 @@
1717
GridTestCase(datasets["ds_2d_left"], "lat", datasets["ds_2d_left"].YG.values),
1818
GridTestCase(datasets["ds_2d_left"], "depth", datasets["ds_2d_left"].ZG.values),
1919
GridTestCase(datasets["ds_2d_left"], "time", datasets["ds_2d_left"].time.values),
20-
GridTestCase(datasets["ds_2d_left"], "xdim", N),
21-
GridTestCase(datasets["ds_2d_left"], "ydim", 2 * N),
22-
GridTestCase(datasets["ds_2d_left"], "zdim", 3 * N),
20+
GridTestCase(datasets["ds_2d_left"], "xdim", X),
21+
GridTestCase(datasets["ds_2d_left"], "ydim", Y),
22+
GridTestCase(datasets["ds_2d_left"], "zdim", Z),
2323
GridTestCase(datasets["ds_2d_left"], "tdim", T),
2424
GridTestCase(datasets["ds_2d_left"], "time_origin", TimeConverter(datasets["ds_2d_left"].time.values[0])),
2525
]

0 commit comments

Comments
 (0)