|
| 1 | +import random |
| 2 | + |
| 3 | +import numpy as np |
| 4 | +import pytest |
| 5 | + |
| 6 | +from parcels._datasets.structured.generic import simple_UV_dataset |
| 7 | +from parcels.application_kernels import DiffusionUniformKh |
| 8 | +from parcels.field import Field, VectorField |
| 9 | +from parcels.fieldset import FieldSet |
| 10 | +from parcels.particleset import ParticleSet |
| 11 | +from parcels.xgrid import _XGRID_AXES, XGrid |
| 12 | + |
| 13 | + |
| 14 | +def BiLinear( # TODO move to interpolation file |
| 15 | + field: Field, |
| 16 | + ti: int, |
| 17 | + position: dict[_XGRID_AXES, tuple[int, float | np.ndarray]], |
| 18 | + tau: np.float32 | np.float64, |
| 19 | + t: np.float32 | np.float64, |
| 20 | + z: np.float32 | np.float64, |
| 21 | + y: np.float32 | np.float64, |
| 22 | + x: np.float32 | np.float64, |
| 23 | +): |
| 24 | + """Bilinear interpolation on a regular grid.""" |
| 25 | + xi, xsi = position["X"] |
| 26 | + yi, eta = position["Y"] |
| 27 | + zi, zeta = position["Z"] |
| 28 | + |
| 29 | + data = field.data.data[:, zi, yi : yi + 2, xi : xi + 2] |
| 30 | + data = (1 - tau) * data[ti, :, :] + tau * data[ti + 1, :, :] |
| 31 | + |
| 32 | + return ( |
| 33 | + (1 - xsi) * (1 - eta) * data[0, 0] |
| 34 | + + xsi * (1 - eta) * data[0, 1] |
| 35 | + + xsi * eta * data[1, 1] |
| 36 | + + (1 - xsi) * eta * data[1, 0] |
| 37 | + ) |
| 38 | + |
| 39 | + |
| 40 | +@pytest.mark.parametrize("mesh_type", ["spherical", "flat"]) |
| 41 | +def test_fieldKh_Brownian(mesh_type): |
| 42 | + kh_zonal = 100 |
| 43 | + kh_meridional = 50 |
| 44 | + mesh_conversion = 1 / 1852.0 / 60 if mesh_type == "spherical" else 1 |
| 45 | + |
| 46 | + ds = simple_UV_dataset(dims=(2, 1, 2, 2), mesh_type=mesh_type) |
| 47 | + ds["lon"].data = np.array([-1e6, 1e6]) |
| 48 | + ds["lat"].data = np.array([-1e6, 1e6]) |
| 49 | + grid = XGrid.from_dataset(ds) |
| 50 | + U = Field("U", ds["U"], grid, mesh_type=mesh_type, interp_method=BiLinear) |
| 51 | + V = Field("V", ds["V"], grid, mesh_type=mesh_type, interp_method=BiLinear) |
| 52 | + ds["Kh_zonal"] = (["time", "depth", "YG", "XG"], np.full((2, 1, 2, 2), kh_zonal)) |
| 53 | + ds["Kh_meridional"] = (["time", "depth", "YG", "XG"], np.full((2, 1, 2, 2), kh_meridional)) |
| 54 | + Kh_zonal = Field("Kh_zonal", ds["Kh_zonal"], grid=grid, mesh_type=mesh_type, interp_method=BiLinear) |
| 55 | + Kh_meridional = Field("Kh_meridional", ds["Kh_meridional"], grid=grid, mesh_type=mesh_type, interp_method=BiLinear) |
| 56 | + UV = VectorField("UV", U, V) |
| 57 | + fieldset = FieldSet([U, V, UV, Kh_zonal, Kh_meridional]) |
| 58 | + |
| 59 | + npart = 100 |
| 60 | + runtime = np.timedelta64(2, "h") |
| 61 | + |
| 62 | + random.seed(1234) |
| 63 | + pset = ParticleSet(fieldset=fieldset, lon=np.zeros(npart), lat=np.zeros(npart)) |
| 64 | + pset.execute(pset.Kernel(DiffusionUniformKh), runtime=runtime, dt=np.timedelta64(1, "h")) |
| 65 | + |
| 66 | + expected_std_lon = np.sqrt(2 * kh_zonal * mesh_conversion**2 * (runtime / np.timedelta64(1, "s"))) |
| 67 | + expected_std_lat = np.sqrt(2 * kh_meridional * mesh_conversion**2 * (runtime / np.timedelta64(1, "s"))) |
| 68 | + |
| 69 | + tol = 500 * mesh_conversion # effectively 500 m errors |
| 70 | + assert np.allclose(np.std(pset.lat), expected_std_lat, atol=tol) |
| 71 | + assert np.allclose(np.std(pset.lon), expected_std_lon, atol=tol) |
| 72 | + assert np.allclose(np.mean(pset.lon), 0, atol=tol) |
| 73 | + assert np.allclose(np.mean(pset.lat), 0, atol=tol) |
0 commit comments