Skip to content

Commit 33f355f

Browse files
Moving last diffusion tests from v3 to v4
And also moving BiLinear interpolation to interpolation.py
1 parent ed9abda commit 33f355f

File tree

4 files changed

+111
-120
lines changed

4 files changed

+111
-120
lines changed

parcels/application_kernels/interpolation.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,41 @@
1010

1111
if TYPE_CHECKING:
1212
from parcels.uxgrid import _UXGRID_AXES
13+
from parcels.xgrid import _XGRID_AXES
1314

1415
__all__ = [
1516
"UXPiecewiseConstantFace",
1617
"UXPiecewiseLinearNode",
18+
"XBiLinear",
1719
]
1820

1921

22+
def XBiLinear(
23+
field: Field,
24+
ti: int,
25+
position: dict[_XGRID_AXES, tuple[int, float | np.ndarray]],
26+
tau: np.float32 | np.float64,
27+
t: np.float32 | np.float64,
28+
z: np.float32 | np.float64,
29+
y: np.float32 | np.float64,
30+
x: np.float32 | np.float64,
31+
):
32+
"""Bilinear interpolation on a regular grid."""
33+
xi, xsi = position["X"]
34+
yi, eta = position["Y"]
35+
zi, zeta = position["Z"]
36+
37+
data = field.data.data[:, zi, yi : yi + 2, xi : xi + 2]
38+
data = (1 - tau) * data[ti, :, :] + tau * data[ti + 1, :, :]
39+
40+
return (
41+
(1 - xsi) * (1 - eta) * data[0, 0]
42+
+ xsi * (1 - eta) * data[0, 1]
43+
+ xsi * eta * data[1, 1]
44+
+ (1 - xsi) * eta * data[1, 0]
45+
)
46+
47+
2048
def UXPiecewiseConstantFace(
2149
field: Field,
2250
ti: int,

tests/test_diffusion.py

Lines changed: 0 additions & 73 deletions
This file was deleted.

tests/utils.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@
44

55
import struct
66
from pathlib import Path
7-
from typing import TYPE_CHECKING
87

98
import numpy as np
109
import xarray as xr
1110

1211
import parcels
13-
from parcels import FieldSet
14-
from parcels.xgrid import _FIELD_DATA_ORDERING, get_axis_from_dim_name
15-
16-
if TYPE_CHECKING:
17-
from parcels.xgrid import XGrid
12+
from parcels._datasets.structured.generic import simple_UV_dataset
13+
from parcels.application_kernels.interpolation import XBiLinear
14+
from parcels.field import Field, VectorField
15+
from parcels.fieldset import FieldSet
16+
from parcels.xgrid import _FIELD_DATA_ORDERING, XGrid, get_axis_from_dim_name
1817

1918
PROJECT_ROOT = Path(__file__).resolve().parents[1]
2019
TEST_ROOT = PROJECT_ROOT / "tests"
@@ -69,13 +68,18 @@ def create_fieldset_global(xdim=200, ydim=100):
6968
return FieldSet.from_data(data, dimensions, mesh="flat")
7069

7170

72-
def create_fieldset_zeros_conversion(mesh="spherical", xdim=200, ydim=100, mesh_conversion=1) -> FieldSet:
71+
def create_fieldset_zeros_conversion(mesh_type="spherical", xdim=200, ydim=100) -> FieldSet:
7372
"""Zero velocity field with lat and lon determined by a conversion factor."""
74-
lon = np.linspace(-1e5 * mesh_conversion, 1e5 * mesh_conversion, xdim, dtype=np.float32)
75-
lat = np.linspace(-1e5 * mesh_conversion, 1e5 * mesh_conversion, ydim, dtype=np.float32)
76-
dimensions = {"lon": lon, "lat": lat}
77-
data = {"U": np.zeros((ydim, xdim), dtype=np.float32), "V": np.zeros((ydim, xdim), dtype=np.float32)}
78-
return FieldSet.from_data(data, dimensions, mesh=mesh)
73+
mesh_conversion = 1 / 1852.0 / 60 if mesh_type == "spherical" else 1
74+
ds = simple_UV_dataset(dims=(2, 1, ydim, xdim), mesh_type=mesh_type)
75+
ds["lon"].data = np.linspace(-1e6 * mesh_conversion, 1e6 * mesh_conversion, xdim)
76+
ds["lat"].data = np.linspace(-1e6 * mesh_conversion, 1e6 * mesh_conversion, ydim)
77+
grid = XGrid.from_dataset(ds)
78+
U = Field("U", ds["U"], grid, mesh_type=mesh_type, interp_method=XBiLinear)
79+
V = Field("V", ds["V"], grid, mesh_type=mesh_type, interp_method=XBiLinear)
80+
81+
UV = VectorField("UV", U, V)
82+
return FieldSet([U, V, UV])
7983

8084

8185
def create_simple_pset(n=1):

tests/v4/test_diffusion.py

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,13 @@
66

77
from parcels._datasets.structured.generic import simple_UV_dataset
88
from parcels.application_kernels import AdvectionDiffusionEM, AdvectionDiffusionM1, DiffusionUniformKh
9+
from parcels.application_kernels.interpolation import XBiLinear
910
from parcels.field import Field, VectorField
1011
from parcels.fieldset import FieldSet
12+
from parcels.particle import Particle, Variable
1113
from parcels.particleset import ParticleSet
12-
from parcels.xgrid import _XGRID_AXES, XGrid
13-
14-
15-
def BiLinear( # TODO move to interpolation file
16-
field: Field,
17-
ti: int,
18-
position: dict[_XGRID_AXES, tuple[int, float | np.ndarray]],
19-
tau: np.float32 | np.float64,
20-
t: np.float32 | np.float64,
21-
z: np.float32 | np.float64,
22-
y: np.float32 | np.float64,
23-
x: np.float32 | np.float64,
24-
):
25-
"""Bilinear interpolation on a regular grid."""
26-
xi, xsi = position["X"]
27-
yi, eta = position["Y"]
28-
zi, zeta = position["Z"]
29-
30-
data = field.data.data[:, zi, yi : yi + 2, xi : xi + 2]
31-
data = (1 - tau) * data[ti, :, :] + tau * data[ti + 1, :, :]
32-
33-
return (
34-
(1 - xsi) * (1 - eta) * data[0, 0]
35-
+ xsi * (1 - eta) * data[0, 1]
36-
+ xsi * eta * data[1, 1]
37-
+ (1 - xsi) * eta * data[1, 0]
38-
)
14+
from parcels.xgrid import XGrid
15+
from tests.utils import create_fieldset_zeros_conversion
3916

4017

4118
@pytest.mark.parametrize("mesh_type", ["spherical", "flat"])
@@ -48,12 +25,12 @@ def test_fieldKh_Brownian(mesh_type):
4825
ds["lon"].data = np.array([-1e6, 1e6])
4926
ds["lat"].data = np.array([-1e6, 1e6])
5027
grid = XGrid.from_dataset(ds)
51-
U = Field("U", ds["U"], grid, mesh_type=mesh_type, interp_method=BiLinear)
52-
V = Field("V", ds["V"], grid, mesh_type=mesh_type, interp_method=BiLinear)
28+
U = Field("U", ds["U"], grid, mesh_type=mesh_type, interp_method=XBiLinear)
29+
V = Field("V", ds["V"], grid, mesh_type=mesh_type, interp_method=XBiLinear)
5330
ds["Kh_zonal"] = (["time", "depth", "YG", "XG"], np.full((2, 1, 2, 2), kh_zonal))
5431
ds["Kh_meridional"] = (["time", "depth", "YG", "XG"], np.full((2, 1, 2, 2), kh_meridional))
55-
Kh_zonal = Field("Kh_zonal", ds["Kh_zonal"], grid=grid, mesh_type=mesh_type, interp_method=BiLinear)
56-
Kh_meridional = Field("Kh_meridional", ds["Kh_meridional"], grid=grid, mesh_type=mesh_type, interp_method=BiLinear)
32+
Kh_zonal = Field("Kh_zonal", ds["Kh_zonal"], grid=grid, mesh_type=mesh_type, interp_method=XBiLinear)
33+
Kh_meridional = Field("Kh_meridional", ds["Kh_meridional"], grid=grid, mesh_type=mesh_type, interp_method=XBiLinear)
5734
UV = VectorField("UV", U, V)
5835
fieldset = FieldSet([U, V, UV, Kh_zonal, Kh_meridional])
5936

@@ -85,17 +62,17 @@ def test_fieldKh_SpatiallyVaryingDiffusion(mesh_type, kernel):
8562
ds["lon"].data = np.linspace(-1e6, 1e6, xdim)
8663
ds["lat"].data = np.linspace(-1e6, 1e6, ydim)
8764
grid = XGrid.from_dataset(ds)
88-
U = Field("U", ds["U"], grid, mesh_type=mesh_type, interp_method=BiLinear)
89-
V = Field("V", ds["V"], grid, mesh_type=mesh_type, interp_method=BiLinear)
65+
U = Field("U", ds["U"], grid, mesh_type=mesh_type, interp_method=XBiLinear)
66+
V = Field("V", ds["V"], grid, mesh_type=mesh_type, interp_method=XBiLinear)
9067

9168
Kh = np.zeros((ydim, xdim), dtype=np.float32)
9269
for x in range(xdim):
9370
Kh[:, x] = np.tanh(ds["lon"][x] / ds["lon"][-1] * 10.0) * xdim / 2.0 + xdim / 2.0 + 100.0
9471

9572
ds["Kh_zonal"] = (["time", "depth", "YG", "XG"], np.full((2, 1, ydim, xdim), Kh))
9673
ds["Kh_meridional"] = (["time", "depth", "YG", "XG"], np.full((2, 1, ydim, xdim), Kh))
97-
Kh_zonal = Field("Kh_zonal", ds["Kh_zonal"], grid=grid, mesh_type=mesh_type, interp_method=BiLinear)
98-
Kh_meridional = Field("Kh_meridional", ds["Kh_meridional"], grid=grid, mesh_type=mesh_type, interp_method=BiLinear)
74+
Kh_zonal = Field("Kh_zonal", ds["Kh_zonal"], grid=grid, mesh_type=mesh_type, interp_method=XBiLinear)
75+
Kh_meridional = Field("Kh_meridional", ds["Kh_meridional"], grid=grid, mesh_type=mesh_type, interp_method=XBiLinear)
9976
UV = VectorField("UV", U, V)
10077
fieldset = FieldSet([U, V, UV, Kh_zonal, Kh_meridional])
10178
fieldset.add_constant("dres", ds["lon"][1] - ds["lon"][0])
@@ -110,3 +87,58 @@ def test_fieldKh_SpatiallyVaryingDiffusion(mesh_type, kernel):
11087
assert np.allclose(np.mean(pset.lon), 0, atol=tol)
11188
assert np.allclose(np.mean(pset.lat), 0, atol=tol)
11289
assert stats.skew(pset.lon) > stats.skew(pset.lat)
90+
91+
92+
@pytest.mark.parametrize("lambd", [1, 5])
93+
def test_randomexponential(lambd):
94+
fieldset = create_fieldset_zeros_conversion()
95+
npart = 1000
96+
97+
# Rate parameter for random.expovariate
98+
fieldset.lambd = lambd
99+
100+
# Set random seed
101+
random.seed(1234)
102+
103+
pset = ParticleSet(fieldset=fieldset, lon=np.zeros(npart), lat=np.zeros(npart), depth=np.zeros(npart))
104+
105+
def vertical_randomexponential(particle, fieldset, time): # pragma: no cover
106+
# Kernel for random exponential variable in depth direction
107+
particle.depth = random.expovariate(fieldset.lambd)
108+
109+
pset.execute(vertical_randomexponential, runtime=np.timedelta64(1, "s"), dt=np.timedelta64(1, "s"))
110+
111+
expected_mean = 1.0 / fieldset.lambd
112+
assert np.allclose(np.mean(pset.depth), expected_mean, rtol=0.1)
113+
114+
115+
@pytest.mark.parametrize("mu", [0.8 * np.pi, np.pi])
116+
@pytest.mark.parametrize("kappa", [2, 4])
117+
def test_randomvonmises(mu, kappa):
118+
npart = 10000
119+
fieldset = create_fieldset_zeros_conversion()
120+
121+
# Parameters for random.vonmisesvariate
122+
fieldset.mu = mu
123+
fieldset.kappa = kappa
124+
125+
# Set random seed
126+
random.seed(1234)
127+
128+
AngleParticle = Particle.add_variable(Variable("angle"))
129+
pset = ParticleSet(
130+
fieldset=fieldset, pclass=AngleParticle, lon=np.zeros(npart), lat=np.zeros(npart), depth=np.zeros(npart)
131+
)
132+
133+
def vonmises(particle, fieldset, time): # pragma: no cover
134+
particle.angle = random.vonmisesvariate(fieldset.mu, fieldset.kappa)
135+
136+
pset.execute(vonmises, runtime=np.timedelta64(1, "s"), dt=np.timedelta64(1, "s"))
137+
138+
angles = np.array([p.angle for p in pset])
139+
140+
assert np.allclose(np.mean(angles), mu, atol=0.1)
141+
vonmises_mean = stats.vonmises.mean(kappa=kappa, loc=mu)
142+
assert np.allclose(np.mean(angles), vonmises_mean, atol=0.1)
143+
vonmises_var = stats.vonmises.var(kappa=kappa, loc=mu)
144+
assert np.allclose(np.var(angles), vonmises_var, atol=0.1)

0 commit comments

Comments
 (0)