88from parcels import FieldSet , ParticleSet
99from parcels ._core ._windowed_array import WindowedArray , maybe_windowed
1010from parcels ._datasets .structured .generated import simple_UV_dataset
11+ from parcels ._datasets .unstructured .generic import _ux_constant_flow_face_centered_2D
1112from parcels .kernels import AdvectionRK2
1213
1314
@@ -71,10 +72,17 @@ def test_windowed_isel_backward_clock_loads_once_and_evicts():
7172 assert max_cache <= 2 # only the bracketing levels resident
7273
7374
74- def test_to_windowed_arrays_wraps_dask_but_not_numpy ():
75- ds = simple_UV_dataset (mesh = "flat" )
76- fset_np = FieldSet .from_sgrid_conventions (ds , mesh = "flat" )
77- fset_dk = FieldSet .from_sgrid_conventions (ds .chunk ({"time" : 1 }), mesh = "flat" )
75+ @pytest .mark .parametrize (
76+ "fset_convention, ds" ,
77+ [
78+ (FieldSet .from_sgrid_conventions , simple_UV_dataset (mesh = "flat" )),
79+ (FieldSet .from_ugrid_conventions , _ux_constant_flow_face_centered_2D ()),
80+ ],
81+ ids = ["structured" , "unstructured" ],
82+ )
83+ def test_windowed_arrays_wraps_dask_but_not_numpy (fset_convention : callable , ds : xr .Dataset ):
84+ fset_np = fset_convention (ds , mesh = "flat" )
85+ fset_dk = fset_convention (ds .chunk ({"time" : 1 }), mesh = "flat" )
7886
7987 # construction is never windowing -- it is opt-in via the fieldset method
8088 assert not isinstance (fset_np .U .data , WindowedArray )
@@ -92,9 +100,16 @@ def test_to_windowed_arrays_wraps_dask_but_not_numpy():
92100 assert fset_dk .U .data .shape == fset_np .U .data .shape
93101
94102
95- def test_to_windowed_arrays_is_idempotent_and_forwards_max_levels ():
96- ds = simple_UV_dataset (mesh = "flat" )
97- fs = FieldSet .from_sgrid_conventions (ds .chunk ({"time" : 1 }), mesh = "flat" )
103+ @pytest .mark .parametrize (
104+ "fset_convention, ds" ,
105+ [
106+ (FieldSet .from_sgrid_conventions , simple_UV_dataset (mesh = "flat" )),
107+ (FieldSet .from_ugrid_conventions , _ux_constant_flow_face_centered_2D ()),
108+ ],
109+ ids = ["structured" , "unstructured" ],
110+ )
111+ def test_to_windowed_arrays_is_idempotent_and_forwards_max_levels (fset_convention : callable , ds : xr .Dataset ):
112+ fs = fset_convention (ds .chunk ({"time" : 1 }), mesh = "flat" )
98113
99114 fs .to_windowed_arrays (max_levels = 3 )
100115 first = fs .U .data
@@ -141,10 +156,10 @@ def test_maybe_windowed_passthrough_for_non_time_leading():
141156
142157@pytest .mark .parametrize ("mesh" , ["flat" , "spherical" ])
143158@pytest .mark .parametrize ("dt_minutes" , [15 , - 15 ], ids = ["forward" , "backward" ])
144- def test_dask_advection_matches_numpy (mesh , dt_minutes ):
159+ def test_dask_advection_matches_numpy_on_structured_grids (mesh , dt_minutes ):
145160 """An identical advection must give identical trajectories whether the field
146161 is numpy-backed or dask-backed (windowed) -- for both forward (dt > 0) and
147- backward (dt < 0) integration.
162+ backward (dt < 0) integration on structured grids .
148163 """
149164 ds = simple_UV_dataset (mesh = mesh )
150165 ds ["U" ].data [:] = 1.0 # steady zonal flow -> in-bounds, deterministic
@@ -162,3 +177,27 @@ def run(chunked):
162177 x_dk , y_dk = run (True )
163178 np .testing .assert_allclose (x_dk , x_np , atol = 1e-9 )
164179 np .testing .assert_allclose (y_dk , y_np , atol = 1e-9 )
180+
181+
182+ @pytest .mark .parametrize ("mesh" , ["flat" , "spherical" ])
183+ @pytest .mark .parametrize ("dt_minutes" , [15 , - 15 ], ids = ["forward" , "backward" ])
184+ def test_dask_advection_matches_numpy_on_unstructured_grids (mesh , dt_minutes ):
185+ """An identical advection must give identical trajectories whether the field
186+ is numpy-backed or dask-backed (windowed) -- for both forward (dt > 0) and
187+ backward (dt < 0) integration on unstructured grids.
188+ """
189+ ds = _ux_constant_flow_face_centered_2D ()
190+
191+ def run (chunked ):
192+ d = ds .chunk ({"time" : 1 }) if chunked else ds
193+ fs = FieldSet .from_ugrid_conventions (d , mesh = mesh )
194+ if chunked :
195+ fs .to_windowed_arrays ()
196+ pset = ParticleSet (fs , x = 10 * np .ones (10 ), y = np .linspace (5 , 15 , 10 ))
197+ pset .execute (AdvectionRK2 , runtime = 3600 , dt = np .timedelta64 (dt_minutes , "m" ))
198+ return np .array (pset .x ), np .array (pset .y )
199+
200+ x_np , y_np = run (False )
201+ x_dk , y_dk = run (True )
202+ np .testing .assert_allclose (x_dk , x_np , atol = 1e-9 )
203+ np .testing .assert_allclose (y_dk , y_np , atol = 1e-9 )
0 commit comments