-
Notifications
You must be signed in to change notification settings - Fork 171
Cleanup tests/test_advection.py fieldset ingestion to use SGRID #2642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4027f80
66236a9
c620076
fba5867
e0d333c
02ebfd2
9a70166
9a5cbc5
9c60e45
9c772c0
862875e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,15 +6,12 @@ | |
| import parcels | ||
| import parcels.tutorial | ||
| from parcels import ( | ||
| Field, | ||
| FieldSet, | ||
| Particle, | ||
| ParticleFile, | ||
| ParticleSet, | ||
| StatusCode, | ||
| Variable, | ||
| VectorField, | ||
| XGrid, | ||
| convert, | ||
| ) | ||
| from parcels._core.utils.time import timedelta_to_float | ||
|
|
@@ -26,7 +23,7 @@ | |
| simple_UV_dataset, | ||
| stommel_gyre_dataset, | ||
| ) | ||
| from parcels.interpolators import CGrid_Velocity, XLinear, XLinear_Velocity | ||
| from parcels._datasets.structured.generic import datasets_sgrid | ||
| from parcels.kernels import ( | ||
|
Comment on lines
7
to
27
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The removal of these lower level imports (Field, XGrid, and interpolators) is really nice for this high level set of tests. |
||
| AdvectionDiffusionEM, | ||
| AdvectionDiffusionM1, | ||
|
|
@@ -188,70 +185,49 @@ def SubmergeParticle(particles, fieldset): # pragma: no cover | |
| assert len(pset) == 0 | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("u", [-0.3, np.array(0.2)]) | ||
| @pytest.mark.parametrize("v", [0.2, np.array(1)]) | ||
| @pytest.mark.parametrize("w", [None, -0.2, np.array(0.7)]) | ||
| def test_length1dimensions(u, v, w): # TODO: Refactor this test to be more readable (and isolate test setup) | ||
| (lon, xdim) = (np.linspace(-10, 10, 21), 21) if isinstance(u, np.ndarray) else (np.array([0]), 1) | ||
| (lat, ydim) = (np.linspace(-15, 15, 31), 31) if isinstance(v, np.ndarray) else (np.array([-4]), 1) | ||
| (depth, zdim) = ( | ||
| (np.linspace(-5, 5, 11), 11) if (isinstance(w, np.ndarray) and w is not None) else (np.array([3]), 1) | ||
| ) | ||
| @pytest.mark.parametrize( | ||
| "u_value, x_slice", [(-0.03, slice(0, 1)), (0.02, slice(None))], ids=["single_u_layer", "full_u"] | ||
| ) | ||
| @pytest.mark.parametrize( | ||
| "v_value, y_slice", [(0.02, slice(0, 1)), (0.1, slice(None))], ids=["single_v_layer", "full_v"] | ||
| ) | ||
| @pytest.mark.parametrize( | ||
| "w_value, z_slice", | ||
| [(None, None), (-0.02, slice(0, 1)), (0.07, slice(None))], | ||
| ids=["no_vertical", "single_w_layer", "full_w"], | ||
| ) | ||
| def test_length1dimensions(u_value, x_slice, v_value, y_slice, w_value, z_slice): | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this test I replaced the dataset entirely using one of the existing sgrid ones in I did need to adjust the velocities and seeding positions though. See
Should be fine right? In the other tests, all other dataset changes are just related to now ingesting from SGRID metadata - keeping things the same in the tests
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think this is fine |
||
| ds = datasets_sgrid["ds_2d_padded_high"].copy()[["U_A_grid", "grid"]] | ||
| ds = ds.isel(time=slice(2)) # TODO make this also work for length-1 time dimensions | ||
| ds = ds.rename({"U_A_grid": "U"}) | ||
| ds["U"] = xr.full_like(ds["U"], u_value) | ||
| ds["V"] = xr.full_like(ds["U"], v_value) | ||
| if w_value is not None: | ||
| ds["W"] = xr.full_like(ds["U"], w_value) | ||
|
|
||
| # Slice dataset | ||
| indexers = {"node_dimension1": x_slice, "node_dimension2": y_slice} | ||
| if w_value: | ||
| indexers.update({"vertical_dimensions_dim1": z_slice}) | ||
| ds = ds.sgrid.isel(indexers) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new |
||
|
|
||
| tdim = 2 # TODO make this also work for length-1 time dimensions | ||
|
VeckoTheGecko marked this conversation as resolved.
|
||
| dims = (tdim, zdim, ydim, xdim) | ||
| U = u * np.ones(dims, dtype=np.float32) | ||
| V = v * np.ones(dims, dtype=np.float32) | ||
| if w is not None: | ||
| W = w * np.ones(dims, dtype=np.float32) | ||
|
|
||
| ds = xr.Dataset( | ||
| { | ||
| "U": (["time", "depth", "YG", "XG"], U), | ||
| "V": (["time", "depth", "YG", "XG"], V), | ||
| }, | ||
| coords={ | ||
| "time": (["time"], [np.timedelta64(0, "s"), np.timedelta64(10, "s")], {"axis": "T"}), | ||
| "depth": (["depth"], depth, {"axis": "Z"}), | ||
| "YC": (["YC"], np.arange(ydim) + 0.5, {"axis": "Y"}), | ||
| "YG": (["YG"], np.arange(ydim), {"axis": "Y", "c_grid_axis_shift": -0.5}), | ||
| "XC": (["XC"], np.arange(xdim) + 0.5, {"axis": "X"}), | ||
| "XG": (["XG"], np.arange(xdim), {"axis": "X", "c_grid_axis_shift": -0.5}), | ||
| "lat": (["YG"], lat, {"axis": "Y", "c_grid_axis_shift": 0.5}), | ||
| "lon": (["XG"], lon, {"axis": "X", "c_grid_axis_shift": -0.5}), | ||
| }, | ||
| ) | ||
| if w: | ||
| ds["W"] = (["time", "depth", "YG", "XG"], W) | ||
|
|
||
| grid = XGrid.from_dataset(ds, mesh="flat") | ||
| U = Field("U", ds["U"], grid, interp_method=XLinear) | ||
| V = Field("V", ds["V"], grid, interp_method=XLinear) | ||
| fields = [U, V, VectorField("UV", U, V, vector_interp_method=XLinear_Velocity)] | ||
| if w: | ||
| W = Field("W", ds["W"], grid, interp_method=XLinear) | ||
| fields.append(VectorField("UVW", U, V, W, vector_interp_method=XLinear_Velocity)) | ||
| fieldset = FieldSet(fields) | ||
|
|
||
| x0, y0, z0 = 2, 8, -4 | ||
| fieldset = FieldSet.from_sgrid_conventions(ds, mesh="flat") | ||
|
|
||
| x0, y0, z0 = 3, 3, 20 | ||
| pset = ParticleSet(fieldset, lon=x0, lat=y0, z=z0) | ||
| kernel = AdvectionRK4 if w is None else AdvectionRK4_3D | ||
| kernel = AdvectionRK4 if w_value is None else AdvectionRK4_3D | ||
| pset.execute(kernel, runtime=np.timedelta64(4, "s"), dt=np.timedelta64(1, "s")) | ||
|
|
||
| assert len(pset.lon) == len([p.lon for p in pset]) | ||
| np.testing.assert_allclose(np.array([p.lon - x0 for p in pset]), 4 * u, atol=1e-6) | ||
| np.testing.assert_allclose(np.array([p.lat - y0 for p in pset]), 4 * v, atol=1e-6) | ||
| if w: | ||
| np.testing.assert_allclose(np.array([p.z - z0 for p in pset]), 4 * w, atol=1e-6) | ||
| np.testing.assert_allclose(np.array([p.lon - x0 for p in pset]), 4 * u_value, atol=1e-5) | ||
| np.testing.assert_allclose(np.array([p.lat - y0 for p in pset]), 4 * v_value, atol=1e-5) | ||
| if w_value: | ||
| np.testing.assert_allclose(np.array([p.z - z0 for p in pset]), 4 * w_value, atol=1e-5) | ||
|
|
||
|
|
||
| def test_radialrotation(npart=10): | ||
| ds = radial_rotation_dataset() | ||
| grid = XGrid.from_dataset(ds, mesh="flat") | ||
| U = parcels.Field("U", ds["U"], grid, interp_method=XLinear) | ||
| V = parcels.Field("V", ds["V"], grid, interp_method=XLinear) | ||
| UV = parcels.VectorField("UV", U, V, vector_interp_method=XLinear_Velocity) | ||
| fieldset = parcels.FieldSet([U, V, UV]) | ||
| fieldset = parcels.FieldSet.from_sgrid_conventions(ds, mesh="flat") | ||
|
|
||
| dt = np.timedelta64(30, "s") | ||
| lon = np.linspace(32, 50, npart) | ||
|
|
@@ -285,22 +261,18 @@ def test_radialrotation(npart=10): | |
| ) | ||
| def test_moving_eddy(kernel, rtol): | ||
| ds = moving_eddy_dataset() | ||
| grid = XGrid.from_dataset(ds, mesh="flat") | ||
| U = Field("U", ds["U"], grid, interp_method=XLinear) | ||
| V = Field("V", ds["V"], grid, interp_method=XLinear) | ||
| if kernel in [AdvectionRK2_3D, AdvectionRK4_3D]: | ||
| # Using W to test 3D advection (assuming same velocity as V) | ||
| W = Field("W", ds["V"], grid, interp_method=XLinear) | ||
| UVW = VectorField("UVW", U, V, W, vector_interp_method=XLinear_Velocity) | ||
| fieldset = FieldSet([U, V, W, UVW]) | ||
| else: | ||
| UV = VectorField("UV", U, V, vector_interp_method=XLinear_Velocity) | ||
| fieldset = FieldSet([U, V, UV]) | ||
| ds["W"] = ds["V"] | ||
|
|
||
| if kernel in [AdvectionDiffusionEM, AdvectionDiffusionM1]: | ||
| # Add zero diffusivity field for diffusion kernels | ||
| ds["Kh"] = (["time", "depth", "YG", "XG"], np.full(ds["U"].shape, 0)) | ||
| fieldset.add_field(Field("Kh", ds["Kh"], grid, interp_method=XLinear), "Kh_zonal") | ||
| fieldset.add_field(Field("Kh", ds["Kh"], grid, interp_method=XLinear), "Kh_meridional") | ||
| ds["Kh_zonal"] = (["time", "depth", "YG", "XG"], np.full(ds["U"].shape, 0)) | ||
| ds["Kh_meridional"] = ds["Kh_zonal"] | ||
|
|
||
| fieldset = FieldSet.from_sgrid_conventions(ds, mesh="flat") | ||
|
|
||
| if kernel in [AdvectionDiffusionEM, AdvectionDiffusionM1]: | ||
| fieldset.add_constant("dres", 0.1) | ||
|
|
||
| start_lon, start_lat, start_z = 12000, 12500, 12500 | ||
|
|
@@ -339,11 +311,7 @@ def truth_moving(x_0, y_0, t): | |
| ) | ||
| def test_decaying_moving_eddy(kernel, rtol): | ||
| ds = decaying_moving_eddy_dataset() | ||
| grid = XGrid.from_dataset(ds, mesh="flat") | ||
| U = Field("U", ds["U"], grid, interp_method=XLinear) | ||
| V = Field("V", ds["V"], grid, interp_method=XLinear) | ||
| UV = VectorField("UV", U, V, vector_interp_method=XLinear_Velocity) | ||
| fieldset = FieldSet([U, V, UV]) | ||
| fieldset = FieldSet.from_sgrid_conventions(ds, mesh="flat") | ||
|
|
||
| start_lon, start_lat = 10000, 10000 | ||
| dt = np.timedelta64(60, "m") | ||
|
|
@@ -388,14 +356,7 @@ def truth_moving(x_0, y_0, t): | |
| @pytest.mark.parametrize("grid_type", ["A", "C"]) | ||
| def test_stommelgyre_fieldset(kernel, rtol, grid_type): | ||
| npart = 2 | ||
| ds = stommel_gyre_dataset(grid_type=grid_type) | ||
| grid = XGrid.from_dataset(ds, mesh="flat") | ||
| vector_interp_method = XLinear_Velocity if grid_type == "A" else CGrid_Velocity | ||
| U = Field("U", ds["U"], grid, interp_method=XLinear) | ||
| V = Field("V", ds["V"], grid, interp_method=XLinear) | ||
| P = Field("P", ds["P"], grid, interp_method=XLinear) | ||
| UV = VectorField("UV", U, V, vector_interp_method=vector_interp_method) | ||
| fieldset = FieldSet([U, V, P, UV]) | ||
| fieldset = FieldSet.from_sgrid_conventions(stommel_gyre_dataset(grid_type=grid_type), mesh="flat") | ||
|
|
||
| dt = np.timedelta64(30, "m") | ||
| runtime = np.timedelta64(1, "D") | ||
|
|
@@ -430,12 +391,7 @@ def UpdateP(particles, fieldset): # pragma: no cover | |
| def test_peninsula_fieldset(kernel, rtol, grid_type): | ||
| npart = 2 | ||
| ds = peninsula_dataset(grid_type=grid_type) | ||
| grid = XGrid.from_dataset(ds, mesh="flat") | ||
| U = Field("U", ds["U"], grid, interp_method=XLinear) | ||
| V = Field("V", ds["V"], grid, interp_method=XLinear) | ||
| P = Field("P", ds["P"], grid, interp_method=XLinear) | ||
| UV = VectorField("UV", U, V, vector_interp_method=XLinear_Velocity) | ||
| fieldset = FieldSet([U, V, P, UV]) | ||
| fieldset = FieldSet.from_sgrid_conventions(ds, mesh="flat") | ||
|
|
||
| dt = np.timedelta64(30, "m") | ||
| runtime = np.timedelta64(23, "h") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be explicit that these datasets use the (now mostly defunct) COMODO conventions.
Given we're relying more on sgrid I think this renaming is good.