Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions docs/user_guide/examples/tutorial_nemo_curvilinear.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
"outputs": [],
"source": [
"u, v = fieldset.UV.eval(\n",
" np.array([0]), np.array([0]), np.array([20]), np.array([50]), applyConversion=False\n",
" np.array([0]), np.array([0]), np.array([20]), np.array([50]), apply_conversion=False\n",
") # do not convert m/s to deg/s\n",
"print(f\"(u, v) = ({u[0]:.3f}, {v[0]:.3f})\")\n",
"assert np.isclose(u, 1.0, atol=1e-3)"
Expand Down Expand Up @@ -298,7 +298,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "test-notebooks",
"display_name": "default",
"language": "python",
"name": "python3"
},
Expand All @@ -312,7 +312,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
"version": "3.14.2"
}
},
"nbformat": 4,
Expand Down
4 changes: 2 additions & 2 deletions docs/user_guide/examples/tutorial_unitconverters.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that you can also interpolate the Field without a unit conversion, by using the `eval()` method and setting `applyConversion=False`, as below\n"
"Note that you can also interpolate the Field without a unit conversion, by using the `eval()` method and setting `apply_conversion=False`, as below\n"
]
},
{
Expand All @@ -174,7 +174,7 @@
" z,\n",
" lat,\n",
" lon,\n",
" applyConversion=False,\n",
" apply_conversion=False,\n",
" )\n",
")"
]
Expand Down
14 changes: 14 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,15 @@ select = [
"ISC001", # single-line-implicit-string-concatenation
"TID", # flake8-tidy-imports
"T100", # Checks for the presence of debugger calls and imports
"N", # pep8 naming conventions
"PGH004", # ensures no blanket noqa's are used
]

exclude = [
"tests-v3/**",
'docs/user_guide/examples_v3/**',
] # TODO v4: Remove once folders are gone

ignore = [
# line too long (82 > 79 characters)
"E501",
Expand All @@ -104,6 +111,13 @@ ignore = [
"RUF043",
"RUF046", # Value being cast to `int` is already an integer

# Parcels specific deviations from PEP8 naming
'N802', # We use function names that are camelcased to denote functions
'N806', # We use capitalized variable names within functions in Parcels to denote fields, as well as multidimensional arrays
'N811', # Well, this just seems like a bad rule O_o
'N816', # TODO: Enable for `src` and consider disabling for notebooks
'N818', # Not all Parcels exceptions should have an "Error" suffix

# TODO: Move this ignore so that it only applies in the tests folder. Do in conjunction with any doc related rules
"RUF059", # Unpacked variable `coords` is never used

Expand Down
15 changes: 10 additions & 5 deletions src/parcels/_core/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def _check_velocitysampling(self):
stacklevel=2,
)

def eval(self, time: datetime, z, y, x, particles=None, applyConversion=True):
def eval(self, time: datetime, z, y, x, particles=None, apply_conversion=True):
"""Interpolate field values in space and time.

We interpolate linearly in time and apply implicit unit
Expand All @@ -222,7 +222,7 @@ def eval(self, time: datetime, z, y, x, particles=None, applyConversion=True):

_update_particle_states_interp_value(particles, value)

if applyConversion:
if apply_conversion:
value = self.units.to_target(value, z, y, x)
return value

Expand All @@ -241,7 +241,12 @@ class VectorField:
"""VectorField class that holds vector field data needed to execute particles."""

def __init__(
self, name: str, U: Field, V: Field, W: Field | None = None, vector_interp_method: Callable | None = None
self,
name: str,
U: Field, # noqa: N803
V: Field, # noqa: N803
W: Field | None = None, # noqa: N803
vector_interp_method: Callable | None = None,
):
_assert_str_and_python_varname(name)

Expand Down Expand Up @@ -287,7 +292,7 @@ def vector_interp_method(self, method: Callable):
assert_same_function_signature(method, ref=ZeroInterpolator_Vector, context="Interpolation")
self._vector_interp_method = method

def eval(self, time: datetime, z, y, x, particles=None, applyConversion=True):
def eval(self, time: datetime, z, y, x, particles=None, apply_conversion=True):
"""Interpolate field values in space and time.

We interpolate linearly in time and apply implicit unit
Expand All @@ -314,7 +319,7 @@ def eval(self, time: datetime, z, y, x, particles=None, applyConversion=True):
else:
(u, v, w) = self._vector_interp_method(particle_positions, grid_positions, self)

if applyConversion:
if apply_conversion:
u = self.U.units.to_target(u, z, y, x)
v = self.V.units.to_target(v, z, y, x)
if "3D" in self.vector_type:
Expand Down
15 changes: 9 additions & 6 deletions src/parcels/_core/fieldset.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ def gridset(self) -> list[BaseGrid]:
grids.append(field.grid)
return grids

def from_copernicusmarine(ds: xr.Dataset):
@classmethod
def from_copernicusmarine(cls, ds: xr.Dataset):
"""Create a FieldSet from a Copernicus Marine Service xarray.Dataset.

Parameters
Expand Down Expand Up @@ -237,9 +238,10 @@ def from_copernicusmarine(ds: xr.Dataset):
vertical_dimensions=(sgrid.DimDimPadding("z_center", "depth", sgrid.Padding.LOW),),
).to_attrs(),
)
return FieldSet.from_sgrid_conventions(ds, mesh="spherical")
return cls.from_sgrid_conventions(ds, mesh="spherical")

def from_fesom2(ds: ux.UxDataset):
@classmethod
def from_fesom2(cls, ds: ux.UxDataset):
"""Create a FieldSet from a FESOM2 uxarray.UxDataset.

Parameters
Expand Down Expand Up @@ -275,10 +277,11 @@ def from_fesom2(ds: ux.UxDataset):
for varname in set(ds.data_vars) - set(fields.keys()):
fields[varname] = Field(varname, ds[varname], grid, _select_uxinterpolator(ds[varname]))

return FieldSet(list(fields.values()))
return cls(list(fields.values()))

@classmethod
def from_sgrid_conventions(
ds: xr.Dataset, mesh: Mesh
cls, ds: xr.Dataset, mesh: Mesh
): # TODO: Update mesh to be discovered from the dataset metadata
"""Create a FieldSet from a dataset using SGRID convention metadata.

Expand Down Expand Up @@ -360,7 +363,7 @@ def from_sgrid_conventions(
for varname in set(ds.data_vars) - set(fields.keys()) - skip_vars:
fields[varname] = Field(varname, ds[varname], grid, XLinear)

return FieldSet(list(fields.values()))
return cls(list(fields.values()))


class CalendarError(Exception): # TODO: Move to a parcels errors module
Expand Down
2 changes: 1 addition & 1 deletion src/parcels/_core/index_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def uxgrid_point_in_cell(grid, y: np.ndarray, x: np.ndarray, yi: np.ndarray, xi:
return is_in_cell, coords


def _triangle_area(A, B, C):
def _triangle_area(A, B, C): # noqa: N803
"""Compute the area of a triangle given by three points."""
d1 = B - A
d2 = C - A
Expand Down
2 changes: 1 addition & 1 deletion src/parcels/_core/particlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def store(self):
def create_new_zarrfile(self):
return self._create_new_zarrfile

def _extend_zarr_dims(self, Z, store, dtype, axis):
def _extend_zarr_dims(self, Z, store, dtype, axis): # noqa: N803
if axis == 1:
a = np.full((Z.shape[0], self.chunks[1]), _DATATYPES_TO_FILL_VALUES[dtype], dtype=dtype)
obs = zarr.group(store=store, overwrite=False)["obs"]
Expand Down
2 changes: 2 additions & 0 deletions src/parcels/_core/utils/sgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class Padding(enum.Enum):

class AttrsSerializable(Protocol):
def to_attrs(self) -> dict[str, str | int]: ...

@classmethod
def from_attrs(cls, d: dict[str, Hashable]) -> Self: ...


Expand Down
4 changes: 2 additions & 2 deletions src/parcels/interpolators.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def _get_corner_data_Agrid(
zi: int,
yi: int,
xi: int,
lenT: int,
lenZ: int,
lenT: int, # noqa: N803
lenZ: int, # noqa: N803
npart: int,
axis_dim: dict[str, str],
) -> np.ndarray:
Expand Down
4 changes: 2 additions & 2 deletions src/parcels/kernels/advection.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def AdvectionAnalytical(particles, fieldset): # pragma: no cover
V0 = V0 * (1 - tau) + tau * direction * fieldset.V.data[ti + 1, yi, xi + 1] * c1 * dz
V1 = V1 * (1 - tau) + tau * direction * fieldset.V.data[ti + 1, yi + 1, xi + 1] * c3 * dz

def compute_ds(F0, F1, r, direction, tol):
def compute_ds(F0, F1, r, direction, tol): # noqa: N803
up = F0 * (1 - r) + F1 * r
r_target = 1.0 if direction * up >= 0.0 else 0.0
B = F0 - F1
Expand Down Expand Up @@ -358,7 +358,7 @@ def compute_ds(F0, F1, r, direction, tol):
s_min = min(abs(ds_x), abs(ds_y), abs(ds_z), abs(ds_t / (dxdy * dz)))

# calculate end position in time s_min
def compute_rs(r, B, delta, s_min):
def compute_rs(r, B, delta, s_min): # noqa: N803
if abs(B) < tol:
return -delta * s_min + r
else:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_advection.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ def test_horizontal_advection_in_3D_flow(npart=10):


@pytest.mark.parametrize("direction", ["up", "down"])
@pytest.mark.parametrize("wErrorThroughSurface", [True, False])
def test_advection_3D_outofbounds(direction, wErrorThroughSurface):
@pytest.mark.parametrize("resubmerge_particle", [True, False])
def test_advection_3D_outofbounds(direction, resubmerge_particle):
ds = simple_UV_dataset(mesh="flat")
ds["W"] = ds["V"].copy() # Just to have W field present
ds["U"].data[:] = 0.01 # Set U to small value (to avoid horizontal out of bounds)
Expand All @@ -131,14 +131,14 @@ def SubmergeParticle(particles, fieldset): # pragma: no cover
particles[inds].state = StatusCode.Evaluate

kernels = [AdvectionRK4_3D]
if wErrorThroughSurface:
if resubmerge_particle:
kernels.append(SubmergeParticle)
kernels.append(DeleteParticle)

pset = ParticleSet(fieldset=fieldset, lon=0.5, lat=0.5, z=0.9)
pset.execute(kernels, runtime=np.timedelta64(10, "s"), dt=np.timedelta64(1, "s"))

if direction == "up" and wErrorThroughSurface:
if direction == "up" and resubmerge_particle:
np.testing.assert_allclose(pset.lon[0], 0.6, atol=1e-5)
np.testing.assert_allclose(pset.z[0], 0, atol=1e-5)
else:
Expand Down
16 changes: 8 additions & 8 deletions tests/test_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,31 +201,31 @@ def test_field_unstructured_z_linear():

# Test above first cell center - for piecewise constant, should return the depth of the first cell center
assert np.isclose(
P.eval(time=[0], z=[10.0], y=[30.0], x=[30.0], applyConversion=False),
P.eval(time=[0], z=[10.0], y=[30.0], x=[30.0], apply_conversion=False),
55.555557,
)
# Test below first cell center, but in the first layer - for piecewise constant, should return the depth of the first cell center
assert np.isclose(
P.eval(time=[0], z=[65.0], y=[30.0], x=[30.0], applyConversion=False),
P.eval(time=[0], z=[65.0], y=[30.0], x=[30.0], apply_conversion=False),
55.555557,
)
# Test bottom layer - for piecewise constant, should return the depth of the of the bottom layer cell center
assert np.isclose(
P.eval(time=[0], z=[900.0], y=[30.0], x=[30.0], applyConversion=False),
P.eval(time=[0], z=[900.0], y=[30.0], x=[30.0], apply_conversion=False),
944.44445801,
)

W = Field(name="W", data=ds.W, grid=grid, interp_method=UxPiecewiseLinearNode)
assert np.isclose(
W.eval(time=[0], z=[10.0], y=[30.0], x=[30.0], applyConversion=False),
W.eval(time=[0], z=[10.0], y=[30.0], x=[30.0], apply_conversion=False),
10.0,
)
assert np.isclose(
W.eval(time=[0], z=[65.0], y=[30.0], x=[30.0], applyConversion=False),
W.eval(time=[0], z=[65.0], y=[30.0], x=[30.0], apply_conversion=False),
65.0,
)
assert np.isclose(
W.eval(time=[0], z=[900.0], y=[30.0], x=[30.0], applyConversion=False),
W.eval(time=[0], z=[900.0], y=[30.0], x=[30.0], apply_conversion=False),
900.0,
)

Expand All @@ -239,13 +239,13 @@ def test_field_constant_in_time():

# Assert that the field can be evaluated at any time, and returns the same value
time = np.datetime64("2000-01-01T00:00:00")
P1 = P.eval(time=time, z=[10.0], y=[30.0], x=[30.0], applyConversion=False)
P1 = P.eval(time=time, z=[10.0], y=[30.0], x=[30.0], apply_conversion=False)
P2 = P.eval(
time=time + np.timedelta64(1, "D"),
z=[10.0],
y=[30.0],
x=[30.0],
applyConversion=False,
apply_conversion=False,
)
assert np.isclose(P1, P2)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def test_interpolation_mesh_type(mesh, npart=10):
assert np.isclose(u, u_expected, atol=1e-7)
assert v == 0.0

assert fieldset.U.eval(time, 0, lat, 0, applyConversion=False) == 1.0
assert fieldset.U.eval(time, 0, lat, 0, apply_conversion=False) == 1


interp_methods = {
Expand Down
16 changes: 8 additions & 8 deletions tests/test_uxarray_fieldset.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,25 +123,25 @@ def test_fesom2_square_delaunay_uniform_z_coordinate_eval():
fieldset = FieldSet([UVW, P, UVW.U, UVW.V, UVW.W])

assert np.isclose(
fieldset.U.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
fieldset.U.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], apply_conversion=False),
1.0,
rtol=1e-3,
atol=1e-6,
)
assert np.isclose(
fieldset.V.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
fieldset.V.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], apply_conversion=False),
1.0,
rtol=1e-3,
atol=1e-6,
)
assert np.isclose(
fieldset.W.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
fieldset.W.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], apply_conversion=False),
0.0,
rtol=1e-3,
atol=1e-6,
)
assert np.isclose(
fieldset.p.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
fieldset.p.eval(time=[0.0], z=[1.0], y=[30.0], x=[30.0], apply_conversion=False),
1.0,
rtol=1e-3,
atol=1e-6,
Expand All @@ -163,7 +163,7 @@ def test_fesom2_square_delaunay_antimeridian_eval():
)
fieldset = FieldSet([P])

assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[-170.0], applyConversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[-180.0], applyConversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[180.0], applyConversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[170.0], applyConversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[-170.0], apply_conversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[-180.0], apply_conversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[180.0], apply_conversion=False), 1.0)
assert np.isclose(fieldset.p.eval(time=[0], z=[1.0], y=[30.0], x=[170.0], apply_conversion=False), 1.0)
Loading