From f0d318974d8c208a56465263a35f4ab76ddde5b9 Mon Sep 17 00:00:00 2001 From: Wyatt Sieminski Date: Fri, 17 Jul 2026 16:01:46 -0400 Subject: [PATCH 1/3] broke test_windowed_arrays_wraps_dask_but_not_numpy into structured and unstructured tests --- tests/test_windowed_array.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/test_windowed_array.py b/tests/test_windowed_array.py index 47a85c8eb..31688fcc5 100644 --- a/tests/test_windowed_array.py +++ b/tests/test_windowed_array.py @@ -8,6 +8,7 @@ from parcels import FieldSet, ParticleSet from parcels._core._windowed_array import WindowedArray, maybe_windowed from parcels._datasets.structured.generated import simple_UV_dataset +from parcels._datasets.unstructured.generic import _ux_constant_flow_face_centered_2D from parcels.kernels import AdvectionRK2 @@ -70,8 +71,7 @@ def test_windowed_isel_backward_clock_loads_once_and_evicts(): assert win.loads == ntime # each time level read exactly once, going backward assert max_cache <= 2 # only the bracketing levels resident - -def test_to_windowed_arrays_wraps_dask_but_not_numpy(): +def test_structured_windowed_arrays_wraps_dask_but_not_numpy(): ds = simple_UV_dataset(mesh="flat") fset_np = FieldSet.from_sgrid_conventions(ds, mesh="flat") fset_dk = FieldSet.from_sgrid_conventions(ds.chunk({"time": 1}), mesh="flat") @@ -91,6 +91,25 @@ def test_to_windowed_arrays_wraps_dask_but_not_numpy(): assert fset_dk.U.data.dims == fset_np.U.data.dims assert fset_dk.U.data.shape == fset_np.U.data.shape +def test_unstructured_windowed_arrays_wraps_dask_but_not_numpy(): + ds = _ux_constant_flow_face_centered_2D() + fset_np = FieldSet.from_ugrid_conventions(ds, mesh="flat") + fset_dk = FieldSet.from_ugrid_conventions(ds.chunk({"time": 1}), mesh="flat") + + # construction is never windowing -- it is opt-in via the fieldset method + assert not isinstance(fset_np.U.data, WindowedArray) + assert not isinstance(fset_dk.U.data, WindowedArray) + assert isinstance(fset_dk.U.data.data, da.Array) # chunked input stays lazy (dask-backed) + + assert fset_np.to_windowed_arrays() is fset_np # chainable + fset_dk.to_windowed_arrays() + + # numpy-backed field is left eager; dask-backed field gets wrapped + assert not isinstance(fset_np.U.data, WindowedArray) + assert isinstance(fset_dk.U.data, WindowedArray) + # transparency: forwarded attributes still behave like the DataArray + assert fset_dk.U.data.dims == fset_np.U.data.dims + assert fset_dk.U.data.shape == fset_np.U.data.shape def test_to_windowed_arrays_is_idempotent_and_forwards_max_levels(): ds = simple_UV_dataset(mesh="flat") From b958f0d7d2e8fb16ca350aadf2f2da09270b0774 Mon Sep 17 00:00:00 2001 From: Wyatt Sieminski Date: Mon, 20 Jul 2026 09:27:17 -0400 Subject: [PATCH 2/3] Parameterized struc/unstruc tests and added unstructured advection test --- tests/test_windowed_array.py | 69 +++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/tests/test_windowed_array.py b/tests/test_windowed_array.py index 31688fcc5..b159db777 100644 --- a/tests/test_windowed_array.py +++ b/tests/test_windowed_array.py @@ -71,10 +71,14 @@ def test_windowed_isel_backward_clock_loads_once_and_evicts(): assert win.loads == ntime # each time level read exactly once, going backward assert max_cache <= 2 # only the bracketing levels resident -def test_structured_windowed_arrays_wraps_dask_but_not_numpy(): - ds = simple_UV_dataset(mesh="flat") - fset_np = FieldSet.from_sgrid_conventions(ds, mesh="flat") - fset_dk = FieldSet.from_sgrid_conventions(ds.chunk({"time": 1}), mesh="flat") +@pytest.mark.parametrize("fset_convention, ds", + [(FieldSet.from_sgrid_conventions, simple_UV_dataset(mesh="flat")), + (FieldSet.from_ugrid_conventions, _ux_constant_flow_face_centered_2D())], + ids=["structured", "unstructured"] + ) +def test_windowed_arrays_wraps_dask_but_not_numpy(fset_convention: callable, ds: xr.Dataset): + fset_np = fset_convention(ds, mesh="flat") + fset_dk = fset_convention(ds.chunk({"time": 1}), mesh="flat") # construction is never windowing -- it is opt-in via the fieldset method assert not isinstance(fset_np.U.data, WindowedArray) @@ -91,29 +95,13 @@ def test_structured_windowed_arrays_wraps_dask_but_not_numpy(): assert fset_dk.U.data.dims == fset_np.U.data.dims assert fset_dk.U.data.shape == fset_np.U.data.shape -def test_unstructured_windowed_arrays_wraps_dask_but_not_numpy(): - ds = _ux_constant_flow_face_centered_2D() - fset_np = FieldSet.from_ugrid_conventions(ds, mesh="flat") - fset_dk = FieldSet.from_ugrid_conventions(ds.chunk({"time": 1}), mesh="flat") - - # construction is never windowing -- it is opt-in via the fieldset method - assert not isinstance(fset_np.U.data, WindowedArray) - assert not isinstance(fset_dk.U.data, WindowedArray) - assert isinstance(fset_dk.U.data.data, da.Array) # chunked input stays lazy (dask-backed) - - assert fset_np.to_windowed_arrays() is fset_np # chainable - fset_dk.to_windowed_arrays() - - # numpy-backed field is left eager; dask-backed field gets wrapped - assert not isinstance(fset_np.U.data, WindowedArray) - assert isinstance(fset_dk.U.data, WindowedArray) - # transparency: forwarded attributes still behave like the DataArray - assert fset_dk.U.data.dims == fset_np.U.data.dims - assert fset_dk.U.data.shape == fset_np.U.data.shape - -def test_to_windowed_arrays_is_idempotent_and_forwards_max_levels(): - ds = simple_UV_dataset(mesh="flat") - fs = FieldSet.from_sgrid_conventions(ds.chunk({"time": 1}), mesh="flat") +@pytest.mark.parametrize("fset_convention, ds", + [(FieldSet.from_sgrid_conventions, simple_UV_dataset(mesh="flat")), + (FieldSet.from_ugrid_conventions, _ux_constant_flow_face_centered_2D())], + ids=["structured", "unstructured"] + ) +def test_to_windowed_arrays_is_idempotent_and_forwards_max_levels(fset_convention: callable, ds: xr.Dataset): + fs = fset_convention(ds.chunk({"time": 1}), mesh="flat") fs.to_windowed_arrays(max_levels=3) first = fs.U.data @@ -160,10 +148,10 @@ def test_maybe_windowed_passthrough_for_non_time_leading(): @pytest.mark.parametrize("mesh", ["flat", "spherical"]) @pytest.mark.parametrize("dt_minutes", [15, -15], ids=["forward", "backward"]) -def test_dask_advection_matches_numpy(mesh, dt_minutes): +def test_dask_advection_matches_numpy_on_structured_grids(mesh, dt_minutes): """An identical advection must give identical trajectories whether the field is numpy-backed or dask-backed (windowed) -- for both forward (dt > 0) and - backward (dt < 0) integration. + backward (dt < 0) integration on structured grids. """ ds = simple_UV_dataset(mesh=mesh) ds["U"].data[:] = 1.0 # steady zonal flow -> in-bounds, deterministic @@ -181,3 +169,26 @@ def run(chunked): x_dk, y_dk = run(True) np.testing.assert_allclose(x_dk, x_np, atol=1e-9) np.testing.assert_allclose(y_dk, y_np, atol=1e-9) + +@pytest.mark.parametrize("mesh", ["flat", "spherical"]) +@pytest.mark.parametrize("dt_minutes", [15, -15], ids=["forward", "backward"]) +def test_dask_advection_matches_numpy_on_unstructured_grids(mesh, dt_minutes): + """An identical advection must give identical trajectories whether the field + is numpy-backed or dask-backed (windowed) -- for both forward (dt > 0) and + backward (dt < 0) integration on unstructured grids. + """ + ds = _ux_constant_flow_face_centered_2D() + + def run(chunked): + d = ds.chunk({"time": 1}) if chunked else ds + fs = FieldSet.from_ugrid_conventions(d, mesh=mesh) + if chunked: + fs.to_windowed_arrays() + pset = ParticleSet(fs, x=10*np.ones(10), y=np.linspace(5, 15, 10)) + pset.execute(AdvectionRK2, runtime=3600, dt=np.timedelta64(dt_minutes, "m")) + return np.array(pset.x), np.array(pset.y) + + x_np, y_np = run(False) + x_dk, y_dk = run(True) + np.testing.assert_allclose(x_dk, x_np, atol=1e-9) + np.testing.assert_allclose(y_dk, y_np, atol=1e-9) From 444a629b46e06c90914926c4874e31e6450c45d4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:35:59 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_windowed_array.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/test_windowed_array.py b/tests/test_windowed_array.py index b159db777..2e7fa963f 100644 --- a/tests/test_windowed_array.py +++ b/tests/test_windowed_array.py @@ -71,11 +71,15 @@ def test_windowed_isel_backward_clock_loads_once_and_evicts(): assert win.loads == ntime # each time level read exactly once, going backward assert max_cache <= 2 # only the bracketing levels resident -@pytest.mark.parametrize("fset_convention, ds", - [(FieldSet.from_sgrid_conventions, simple_UV_dataset(mesh="flat")), - (FieldSet.from_ugrid_conventions, _ux_constant_flow_face_centered_2D())], - ids=["structured", "unstructured"] - ) + +@pytest.mark.parametrize( + "fset_convention, ds", + [ + (FieldSet.from_sgrid_conventions, simple_UV_dataset(mesh="flat")), + (FieldSet.from_ugrid_conventions, _ux_constant_flow_face_centered_2D()), + ], + ids=["structured", "unstructured"], +) def test_windowed_arrays_wraps_dask_but_not_numpy(fset_convention: callable, ds: xr.Dataset): fset_np = fset_convention(ds, mesh="flat") fset_dk = fset_convention(ds.chunk({"time": 1}), mesh="flat") @@ -95,11 +99,15 @@ def test_windowed_arrays_wraps_dask_but_not_numpy(fset_convention: callable, ds: assert fset_dk.U.data.dims == fset_np.U.data.dims assert fset_dk.U.data.shape == fset_np.U.data.shape -@pytest.mark.parametrize("fset_convention, ds", - [(FieldSet.from_sgrid_conventions, simple_UV_dataset(mesh="flat")), - (FieldSet.from_ugrid_conventions, _ux_constant_flow_face_centered_2D())], - ids=["structured", "unstructured"] - ) + +@pytest.mark.parametrize( + "fset_convention, ds", + [ + (FieldSet.from_sgrid_conventions, simple_UV_dataset(mesh="flat")), + (FieldSet.from_ugrid_conventions, _ux_constant_flow_face_centered_2D()), + ], + ids=["structured", "unstructured"], +) def test_to_windowed_arrays_is_idempotent_and_forwards_max_levels(fset_convention: callable, ds: xr.Dataset): fs = fset_convention(ds.chunk({"time": 1}), mesh="flat") @@ -170,6 +178,7 @@ def run(chunked): np.testing.assert_allclose(x_dk, x_np, atol=1e-9) np.testing.assert_allclose(y_dk, y_np, atol=1e-9) + @pytest.mark.parametrize("mesh", ["flat", "spherical"]) @pytest.mark.parametrize("dt_minutes", [15, -15], ids=["forward", "backward"]) def test_dask_advection_matches_numpy_on_unstructured_grids(mesh, dt_minutes): @@ -184,7 +193,7 @@ def run(chunked): fs = FieldSet.from_ugrid_conventions(d, mesh=mesh) if chunked: fs.to_windowed_arrays() - pset = ParticleSet(fs, x=10*np.ones(10), y=np.linspace(5, 15, 10)) + pset = ParticleSet(fs, x=10 * np.ones(10), y=np.linspace(5, 15, 10)) pset.execute(AdvectionRK2, runtime=3600, dt=np.timedelta64(dt_minutes, "m")) return np.array(pset.x), np.array(pset.y)