Skip to content

Commit 01dbffa

Browse files
Adding support and test for DiffusionUniformKh
1 parent 72b810c commit 01dbffa

3 files changed

Lines changed: 76 additions & 38 deletions

File tree

parcels/application_kernels/advectiondiffusion.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ def DiffusionUniformKh(particle, fieldset, time): # pragma: no cover
100100
The Wiener increment `dW` is normally distributed with zero
101101
mean and a standard deviation of sqrt(dt).
102102
"""
103+
dt = particle.dt / np.timedelta64(1, "s") # noqa TODO improve API for converting dt to seconds
103104
# Wiener increment with zero mean and std of sqrt(dt)
104-
dWx = random.normalvariate(0, math.sqrt(math.fabs(particle.dt)))
105-
dWy = random.normalvariate(0, math.sqrt(math.fabs(particle.dt)))
105+
dWx = random.normalvariate(0, math.sqrt(math.fabs(dt)))
106+
dWy = random.normalvariate(0, math.sqrt(math.fabs(dt)))
106107

107108
bx = math.sqrt(2 * fieldset.Kh_zonal[particle])
108109
by = math.sqrt(2 * fieldset.Kh_meridional[particle])

tests/test_diffusion.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from parcels import (
99
AdvectionDiffusionEM,
1010
AdvectionDiffusionM1,
11-
DiffusionUniformKh,
1211
Field,
1312
Particle,
1413
ParticleSet,
@@ -17,41 +16,6 @@
1716
from tests.utils import create_fieldset_zeros_conversion
1817

1918

20-
@pytest.mark.v4alpha
21-
@pytest.mark.xfail(reason="GH1946")
22-
@pytest.mark.parametrize("mesh", ["spherical", "flat"])
23-
def test_fieldKh_Brownian(mesh):
24-
xdim = 200
25-
ydim = 100
26-
kh_zonal = 100
27-
kh_meridional = 50
28-
29-
mesh_conversion = 1 / 1852.0 / 60 if mesh == "spherical" else 1
30-
fieldset = create_fieldset_zeros_conversion(mesh=mesh, xdim=xdim, ydim=ydim, mesh_conversion=mesh_conversion)
31-
32-
fieldset.add_constant_field("Kh_zonal", kh_zonal, mesh=mesh)
33-
fieldset.add_constant_field("Kh_meridional", kh_meridional, mesh=mesh)
34-
35-
npart = 1000
36-
runtime = timedelta(days=1)
37-
38-
random.seed(1234)
39-
pset = ParticleSet(fieldset=fieldset, pclass=Particle, lon=np.zeros(npart), lat=np.zeros(npart))
40-
pset.execute(pset.Kernel(DiffusionUniformKh), runtime=runtime, dt=timedelta(hours=1))
41-
42-
expected_std_lon = np.sqrt(2 * kh_zonal * mesh_conversion**2 * runtime.total_seconds())
43-
expected_std_lat = np.sqrt(2 * kh_meridional * mesh_conversion**2 * runtime.total_seconds())
44-
45-
lats = pset.lat
46-
lons = pset.lon
47-
48-
tol = 500 * mesh_conversion # effectively 500 m errors
49-
assert np.allclose(np.std(lats), expected_std_lat, atol=tol)
50-
assert np.allclose(np.std(lons), expected_std_lon, atol=tol)
51-
assert np.allclose(np.mean(lons), 0, atol=tol)
52-
assert np.allclose(np.mean(lats), 0, atol=tol)
53-
54-
5519
@pytest.mark.v4alpha
5620
@pytest.mark.xfail(reason="GH1946")
5721
@pytest.mark.parametrize("mesh", ["spherical", "flat"])

tests/v4/test_diffusion.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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

Comments
 (0)