Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0c4fa45
Added SphericalMesh object to new mesh.py module. Contains only plane…
maureenjcohen Jul 7, 2026
3ddff60
Add SphericalMesh object to imports in __init__.py.
maureenjcohen Jul 7, 2026
ffa6d85
Added SphericalMesh object unpacking to Xgrid.
maureenjcohen Jul 7, 2026
f566fe1
Switched self._radius = mesh.radius to None in second branch of if/el…
maureenjcohen Jul 7, 2026
69fa293
Applied SphericalMesh/radius imports to Uxgrid as well.
maureenjcohen Jul 7, 2026
6ec7382
Adjust assert_valid_mesh in typing to allow SphericalMesh to pass.
maureenjcohen Jul 7, 2026
b10e32c
Bugfix: missed colon in if isinstance(value, SphericalMesh) statement.
maureenjcohen Jul 7, 2026
493ef8f
Moved SphericalMesh import to top rather than within function.
maureenjcohen Jul 7, 2026
08fec2c
Replaced hard-coded instances of 1852 * 60 with calls to deg2m attrib…
maureenjcohen Jul 7, 2026
a277913
Added three tests: test that deg2m calculation works, test calculatio…
maureenjcohen Jul 7, 2026
da32a50
Added a check to SphericalMesh init to catch bad values of radius: no…
maureenjcohen Jul 7, 2026
72be313
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 9, 2026
0c3c1f0
Update src/parcels/_core/mesh.py
maureenjcohen Jul 10, 2026
ddac215
Update src/parcels/_core/mesh.py
maureenjcohen Jul 10, 2026
d28fa4c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 10, 2026
ce76ee3
Update src/parcels/_core/uxgrid.py
maureenjcohen Jul 10, 2026
f781eeb
Modified radius setting so default is EARTH_RADIUS (specified in mesh…
maureenjcohen Jul 13, 2026
9ac08fc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/parcels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from parcels._core.basegrid import BaseGrid
from parcels._core.uxgrid import UxGrid
from parcels._core.xgrid import XGrid
from parcels._core.mesh import SphericalMesh

from parcels._core.statuscodes import (
AllParcelsErrorCodes,
Expand Down Expand Up @@ -54,6 +55,7 @@
"BaseGrid",
"UxGrid",
"XGrid",
"SphericalMesh",
# Status codes and errors
"AllParcelsErrorCodes",
"FieldInterpolationError",
Expand Down
2 changes: 1 addition & 1 deletion src/parcels/_core/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def check_fieldsets_in_kernels(self, kernel): # TODO v4: this can go into anoth
self.fieldset.add_context("RK45_tol", 10)
if self.fieldset.U.grid._mesh == "spherical":
self.fieldset.RK45_tol /= (
1852 * 60
self.fieldset.U.grid.deg2m
) # TODO does not account for zonal variation in meter -> degree conversion
if not hasattr(self.fieldset, "RK45_min_dt"):
warnings.warn(
Expand Down
26 changes: 26 additions & 0 deletions src/parcels/_core/mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import numpy as np

EARTH_RADIUS = 6366707.019493707


class SphericalMesh:
"""Spherical mesh object with configurable planetary radius.

Pass to FieldSet object as ``mesh=SphericalMesh(radius=...)``.
radius is in meters; defaults to Earth radius.
"""

def __init__(self, radius: float = EARTH_RADIUS):
if not isinstance(radius, (int, float, np.number)):
raise TypeError(f"radius must be a number, got {type(radius).__name__}")
if radius <= 0:
raise ValueError(f"radius must be positive, got {radius}")
self.radius = radius

@property
def deg2m(self) -> float:
"""Meters per degree of arc."""
return self.radius * np.pi / 180.0

def __repr__(self) -> str:
return f"SphericalMesh(radius={self.radius})"
14 changes: 8 additions & 6 deletions src/parcels/_core/utils/interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ def dphidxsi3D_lin(zeta: float, eta: float, xsi: float) -> tuple[list[float], li


def dxdxsi3D_lin(
hexa_z: list[float], hexa_y: list[float], hexa_x: list[float], zeta: float, eta: float, xsi: float, mesh: Mesh
hexa_z: list[float], hexa_y: list[float], hexa_x: list[float], zeta: float, eta: float, xsi: float, mesh: Mesh,
deg2m: float = 1852 * 60.0
) -> tuple[float, float, float, float, float, float, float, float, float]:
dphidxsi, dphideta, dphidzet = dphidxsi3D_lin(zeta, eta, xsi)

if mesh == 'spherical':
deg2m = 1852 * 60.
rad = np.pi / 180.
lat = (1-xsi) * (1-eta) * hexa_y[0] + \
xsi * (1-eta) * hexa_y[1] + \
Expand All @@ -92,9 +92,10 @@ def dxdxsi3D_lin(


def jacobian3D_lin(
hexa_z: list[float], hexa_y: list[float], hexa_x: list[float], zeta: float, eta: float, xsi: float, mesh: Mesh
hexa_z: list[float], hexa_y: list[float], hexa_x: list[float], zeta: float, eta: float, xsi: float, mesh: Mesh,
deg2m: float = 1852 * 60.0
) -> float:
dxdxsi, dxdeta, dxdzet, dydxsi, dydeta, dydzet, dzdxsi, dzdeta, dzdzet = dxdxsi3D_lin(hexa_z, hexa_y, hexa_x, zeta, eta, xsi, mesh)
dxdxsi, dxdeta, dxdzet, dydxsi, dydeta, dydzet, dzdxsi, dzdeta, dzdzet = dxdxsi3D_lin(hexa_z, hexa_y, hexa_x, zeta, eta, xsi, mesh, deg2m)

jac = (
dxdxsi * (dydeta * dzdzet - dzdeta * dydzet)
Expand Down Expand Up @@ -174,10 +175,11 @@ def interpolate(phi: Callable[[float], list[float]], f: list[float], xsi: float)
return np.dot(phi(xsi), f)


def _geodetic_distance(lat1: float, lat2: float, lon1: float, lon2: float, mesh: Mesh, lat: float) -> float:
def _geodetic_distance(
lat1: float, lat2: float, lon1: float, lon2: float, mesh: Mesh, lat: float, deg2m: float = 1852 * 60.0
) -> float:
if mesh == "spherical":
rad = np.pi / 180.0
deg2m = 1852 * 60.0
return np.sqrt(((lon2 - lon1) * deg2m * np.cos(rad * lat)) ** 2 + ((lat2 - lat1) * deg2m) ** 2)
else:
return np.sqrt((lon2 - lon1) ** 2 + (lat2 - lat1) ** 2)
Expand Down
15 changes: 14 additions & 1 deletion src/parcels/_core/uxgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from parcels._core.basegrid import BaseGrid
from parcels._core.index_search import GRID_SEARCH_ERROR, _search_1d_array, uxgrid_point_in_cell
from parcels._core.mesh import EARTH_RADIUS, SphericalMesh
from parcels._typing import assert_valid_mesh

_UXGRID_AXES = Literal["Z", "FACE"]
Expand Down Expand Up @@ -41,7 +42,12 @@ def __init__(self, grid: ux.grid.Grid, z: ux.UxDataArray, mesh) -> None:
if z.ndim != 1:
raise ValueError("z must be a 1D array of vertical coordinates")
self.z = z
self._mesh = mesh
if isinstance(mesh, SphericalMesh):
self._mesh = "spherical"
self._radius = mesh.radius
else:
self._mesh = mesh
self._radius = EARTH_RADIUS if mesh == "spherical" else None
self._spatialhash = None

assert_valid_mesh(mesh)
Expand Down Expand Up @@ -73,6 +79,13 @@ def get_axis_dim(self, axis: _UXGRID_AXES) -> int:
elif axis == "FACE":
return self.uxgrid.n_face

@property
def deg2m(self) -> float:
"""Metres per arcdegree for this grid's mesh."""
if self._radius is None: # flat mesh; None causes crash in advection
return 1.0
return self._radius * np.pi / 180.0

def search(self, z, y, x, ei=None, tol=1e-6):
"""
Search for the grid cell (face) and vertical layer that contains the given points.
Expand Down
15 changes: 14 additions & 1 deletion src/parcels/_core/xgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import parcels._typing as ptyping
from parcels._core.basegrid import BaseGrid
from parcels._core.index_search import _search_1d_array, _search_indices_curvilinear_2d
from parcels._core.mesh import EARTH_RADIUS, SphericalMesh
from parcels._sgrid.accessor import _get_dim_to_axis_mapping
from parcels._sgrid.core import SGRID_PADDING_TO_XGCM_POSITION

Expand Down Expand Up @@ -169,7 +170,12 @@ def __init__(self, model_data: xr.Dataset, mesh):
self._ds = model_data
grid = XgcmLikeGrid(self.sgrid_metadata, model_data)
self.xgcm_grid = grid
self._mesh = mesh
if isinstance(mesh, SphericalMesh):
self._mesh = "spherical"
self._radius = mesh.radius
Comment on lines +173 to +175

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR looks good to me! The only question I have for @VeckoTheGecko is if this combination of grid._mesh as a string and the SphericalMesh class is not too obfuscating/confusing. Would it be worth to streamline this and e.g. use the class throughout? Or shall we do that in a next PR?

else:
self._mesh = mesh
self._radius = EARTH_RADIUS if mesh == "spherical" else None
self._spatialhash = None
ds = model_data

Expand Down Expand Up @@ -249,6 +255,13 @@ def _datetimes(self):
def time(self):
return self._datetimes.astype(np.float64) / 1e9

@property
def deg2m(self) -> float:
"""Metres per degree of arc for this grid's mesh."""
if self._radius is None: # flat mesh; None causes crash in advection
return 1.0
return self._radius * np.pi / 180.0

@cached_property
def xdim(self) -> int:
return self.get_axis_dim("X")
Expand Down
4 changes: 4 additions & 0 deletions src/parcels/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import numpy as np
from cftime import datetime as cftime_datetime

from parcels._core.mesh import SphericalMesh

if TYPE_CHECKING:
import xgcm

Expand Down Expand Up @@ -73,4 +75,6 @@ def _validate_against_pure_literal(value, typing_literal):

# Assertion functions to clean user input
def assert_valid_mesh(value: Any):
if isinstance(value, SphericalMesh):
return
Comment on lines +78 to +79

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VeckoTheGecko, can you check if this is correct behaviour for typing?

_validate_against_pure_literal(value, Mesh)
4 changes: 2 additions & 2 deletions src/parcels/interpolators/_uxinterpolators.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ def interp(
u = vectorfield.U.interp_method.interp(particle_positions, grid_positions, vectorfield.U)
v = vectorfield.V.interp_method.interp(particle_positions, grid_positions, vectorfield.V)
if vectorfield.grid._mesh == "spherical":
u /= 1852 * 60 * np.cos(np.deg2rad(particle_positions["y"]))
v /= 1852 * 60
u /= vectorfield.grid.deg2m * np.cos(np.deg2rad(particle_positions["y"]))
v /= vectorfield.grid.deg2m

if "3D" in vectorfield.vector_type:
w = vectorfield.W.interp_method.interp(particle_positions, grid_positions, vectorfield.W)
Expand Down
16 changes: 8 additions & 8 deletions src/parcels/interpolators/_xinterpolators.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ def interp(
u = _xlinear.interp(particle_positions, grid_positions, vectorfield.U)
v = _xlinear.interp(particle_positions, grid_positions, vectorfield.V)
if vectorfield.grid._mesh == "spherical":
u /= 1852 * 60 * np.cos(np.deg2rad(particle_positions["y"]))
v /= 1852 * 60
u /= vectorfield.grid.deg2m * np.cos(np.deg2rad(particle_positions["y"]))
v /= vectorfield.grid.deg2m

if vectorfield.W:
w = _xlinear.interp(particle_positions, grid_positions, vectorfield.W)
Expand Down Expand Up @@ -201,16 +201,16 @@ def interp(
px[1:] = np.where(px[1:] - px[0] > 180, px[1:] - 360, px[1:])
px[1:] = np.where(-px[1:] + px[0] > 180, px[1:] + 360, px[1:])
c1 = i_u._geodetic_distance(
py[0], py[1], px[0], px[1], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(0.0, xsi), py)
py[0], py[1], px[0], px[1], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(0.0, xsi), py), grid.deg2m
)
c2 = i_u._geodetic_distance(
py[1], py[2], px[1], px[2], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(eta, 1.0), py)
py[1], py[2], px[1], px[2], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(eta, 1.0), py), grid.deg2m
)
c3 = i_u._geodetic_distance(
py[2], py[3], px[2], px[3], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(1.0, xsi), py)
py[2], py[3], px[2], px[3], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(1.0, xsi), py), grid.deg2m
)
c4 = i_u._geodetic_distance(
py[3], py[0], px[3], px[0], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(eta, 0.0), py)
py[3], py[0], px[3], px[0], grid._mesh, np.einsum("ij,ji->i", i_u.phi2D_lin(eta, 0.0), py), grid.deg2m
)

def _create_selection_dict(dims, zdir=False):
Expand Down Expand Up @@ -283,7 +283,7 @@ def _compute_corner_data(data, selection_dict) -> np.ndarray:
Vvel = (1 - eta) * V0 + eta * V1

if grid._mesh == "spherical":
jac = i_u._compute_jacobian_determinant(py, px, eta, xsi) * 1852 * 60.0
jac = i_u._compute_jacobian_determinant(py, px, eta, xsi) * grid.deg2m
else:
jac = i_u._compute_jacobian_determinant(py, px, eta, xsi)

Expand All @@ -304,7 +304,7 @@ def _compute_corner_data(data, selection_dict) -> np.ndarray:
v = v.compute()

if grid._mesh == "spherical":
conversion = 1852 * 60.0 * np.cos(np.deg2rad(particle_positions["y"]))
conversion = grid.deg2m * np.cos(np.deg2rad(particle_positions["y"]))
u /= conversion
v /= conversion

Expand Down
10 changes: 5 additions & 5 deletions src/parcels/kernels/_advection.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,12 @@ def AdvectionAnalytical(particles, fieldset): # pragma: no cover
else:
dz = 1.0

c1 = i_u._geodetic_distance(py[0], py[1], px[0], px[1], grid.mesh, np.dot(i_u.phi2D_lin(0.0, xsi), py))
c2 = i_u._geodetic_distance(py[1], py[2], px[1], px[2], grid.mesh, np.dot(i_u.phi2D_lin(eta, 1.0), py))
c3 = i_u._geodetic_distance(py[2], py[3], px[2], px[3], grid.mesh, np.dot(i_u.phi2D_lin(1.0, xsi), py))
c4 = i_u._geodetic_distance(py[3], py[0], px[3], px[0], grid.mesh, np.dot(i_u.phi2D_lin(eta, 0.0), py))
c1 = i_u._geodetic_distance(py[0], py[1], px[0], px[1], grid.mesh, np.dot(i_u.phi2D_lin(0.0, xsi), py), grid.deg2m)
c2 = i_u._geodetic_distance(py[1], py[2], px[1], px[2], grid.mesh, np.dot(i_u.phi2D_lin(eta, 1.0), py), grid.deg2m)
c3 = i_u._geodetic_distance(py[2], py[3], px[2], px[3], grid.mesh, np.dot(i_u.phi2D_lin(1.0, xsi), py), grid.deg2m)
c4 = i_u._geodetic_distance(py[3], py[0], px[3], px[0], grid.mesh, np.dot(i_u.phi2D_lin(eta, 0.0), py), grid.deg2m)
rad = np.pi / 180.0
deg2m = 1852 * 60.0
deg2m = grid.deg2m
meshJac = (deg2m * deg2m * math.cos(rad * particles.y)) if grid.mesh == "spherical" else 1
dxdy = i_u._compute_jacobian_determinant(py, px, eta, xsi) * meshJac

Expand Down
36 changes: 18 additions & 18 deletions src/parcels/kernels/_advectiondiffusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
__all__ = ["AdvectionDiffusionEM", "AdvectionDiffusionM1", "DiffusionUniformKh"]


def meters_to_degrees_zonal(deg, lat): # pragma: no cover
def meters_to_degrees_zonal(deg, lat, deg2m): # pragma: no cover
"""Convert square meters to square degrees longitude at a given latitude."""
return deg / pow(1852 * 60.0 * np.cos(lat * np.pi / 180), 2)
return deg / pow(deg2m * np.cos(lat * np.pi / 180), 2)


def meters_to_degrees_meridional(deg): # pragma: no cover
def meters_to_degrees_meridional(deg, deg2m): # pragma: no cover
"""Convert square meters to square degrees latitude."""
return deg / pow(1852 * 60.0, 2)
return deg / pow(deg2m, 2)


def AdvectionDiffusionM1(particles, fieldset): # pragma: no cover
Expand All @@ -40,26 +40,26 @@ def AdvectionDiffusionM1(particles, fieldset): # pragma: no cover
Kxp1 = fieldset.Kh_zonal[particles.t, particles.z, particles.y, particles.x + fieldset.dres, particles]
Kxm1 = fieldset.Kh_zonal[particles.t, particles.z, particles.y, particles.x - fieldset.dres, particles]
if fieldset.Kh_zonal.grid._mesh == "spherical":
Kxp1 = meters_to_degrees_zonal(Kxp1, particles.y)
Kxm1 = meters_to_degrees_zonal(Kxm1, particles.y)
Kxp1 = meters_to_degrees_zonal(Kxp1, particles.y, fieldset.Kh_zonal.grid.deg2m)
Kxm1 = meters_to_degrees_zonal(Kxm1, particles.y, fieldset.Kh_zonal.grid.deg2m)
dKdx = (Kxp1 - Kxm1) / (2 * fieldset.dres)

u, v = fieldset.UV[particles.t, particles.z, particles.y, particles.x, particles]
kh_zonal = fieldset.Kh_zonal[particles.t, particles.z, particles.y, particles.x, particles]
if fieldset.Kh_zonal.grid._mesh == "spherical":
kh_zonal = meters_to_degrees_zonal(kh_zonal, particles.y)
kh_zonal = meters_to_degrees_zonal(kh_zonal, particles.y, fieldset.Kh_zonal.grid.deg2m)
bx = np.sqrt(2 * kh_zonal)

Kyp1 = fieldset.Kh_meridional[particles.t, particles.z, particles.y + fieldset.dres, particles.x, particles]
Kym1 = fieldset.Kh_meridional[particles.t, particles.z, particles.y - fieldset.dres, particles.x, particles]
if fieldset.Kh_meridional.grid._mesh == "spherical":
Kyp1 = meters_to_degrees_meridional(Kyp1)
Kym1 = meters_to_degrees_meridional(Kym1)
Kyp1 = meters_to_degrees_meridional(Kyp1, fieldset.Kh_meridional.grid.deg2m)
Kym1 = meters_to_degrees_meridional(Kym1, fieldset.Kh_meridional.grid.deg2m)
dKdy = (Kyp1 - Kym1) / (2 * fieldset.dres)

kh_meridional = fieldset.Kh_meridional[particles.t, particles.z, particles.y, particles.x, particles]
if fieldset.Kh_meridional.grid._mesh == "spherical":
kh_meridional = meters_to_degrees_meridional(kh_meridional)
kh_meridional = meters_to_degrees_meridional(kh_meridional, fieldset.Kh_meridional.grid.deg2m)
by = np.sqrt(2 * kh_meridional)

# Particle positions are updated only after evaluating all terms.
Expand Down Expand Up @@ -89,27 +89,27 @@ def AdvectionDiffusionEM(particles, fieldset): # pragma: no cover
Kxp1 = fieldset.Kh_zonal[particles.t, particles.z, particles.y, particles.x + fieldset.dres, particles]
Kxm1 = fieldset.Kh_zonal[particles.t, particles.z, particles.y, particles.x - fieldset.dres, particles]
if fieldset.Kh_zonal.grid._mesh == "spherical":
Kxp1 = meters_to_degrees_zonal(Kxp1, particles.y)
Kxm1 = meters_to_degrees_zonal(Kxm1, particles.y)
Kxp1 = meters_to_degrees_zonal(Kxp1, particles.y, fieldset.Kh_zonal.grid.deg2m)
Kxm1 = meters_to_degrees_zonal(Kxm1, particles.y, fieldset.Kh_zonal.grid.deg2m)
dKdx = (Kxp1 - Kxm1) / (2 * fieldset.dres)
ax = u + dKdx

kh_zonal = fieldset.Kh_zonal[particles.t, particles.z, particles.y, particles.x, particles]
if fieldset.Kh_zonal.grid._mesh == "spherical":
kh_zonal = meters_to_degrees_zonal(kh_zonal, particles.y)
kh_zonal = meters_to_degrees_zonal(kh_zonal, particles.y, fieldset.Kh_zonal.grid.deg2m)
bx = np.sqrt(2 * kh_zonal)

Kyp1 = fieldset.Kh_meridional[particles.t, particles.z, particles.y + fieldset.dres, particles.x, particles]
Kym1 = fieldset.Kh_meridional[particles.t, particles.z, particles.y - fieldset.dres, particles.x, particles]
if fieldset.Kh_meridional.grid._mesh == "spherical":
Kyp1 = meters_to_degrees_meridional(Kyp1)
Kym1 = meters_to_degrees_meridional(Kym1)
Kyp1 = meters_to_degrees_meridional(Kyp1, fieldset.Kh_meridional.grid.deg2m)
Kym1 = meters_to_degrees_meridional(Kym1, fieldset.Kh_meridional.grid.deg2m)
dKdy = (Kyp1 - Kym1) / (2 * fieldset.dres)
ay = v + dKdy

kh_meridional = fieldset.Kh_meridional[particles.t, particles.z, particles.y, particles.x, particles]
if fieldset.Kh_meridional.grid._mesh == "spherical":
kh_meridional = meters_to_degrees_meridional(kh_meridional)
kh_meridional = meters_to_degrees_meridional(kh_meridional, fieldset.Kh_meridional.grid.deg2m)
by = np.sqrt(2 * kh_meridional)

# Particle positions are updated only after evaluating all terms.
Expand Down Expand Up @@ -143,8 +143,8 @@ def DiffusionUniformKh(particles, fieldset): # pragma: no cover
kh_meridional = fieldset.Kh_meridional[particles]

if fieldset.Kh_zonal.grid._mesh == "spherical":
kh_zonal = meters_to_degrees_zonal(kh_zonal, particles.y)
kh_meridional = meters_to_degrees_meridional(kh_meridional)
kh_zonal = meters_to_degrees_zonal(kh_zonal, particles.y, fieldset.Kh_zonal.grid.deg2m)
kh_meridional = meters_to_degrees_meridional(kh_meridional, fieldset.Kh_meridional.grid.deg2m)

bx = np.sqrt(2 * kh_zonal)
by = np.sqrt(2 * kh_meridional)
Expand Down
Loading