Skip to content
Open
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
4 changes: 2 additions & 2 deletions docs/user_guide/examples_v3/example_brownian.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def test_brownian_example(mesh, npart=3000):
pset = parcels.ParticleSet(
fieldset=fieldset,
pclass=parcels.Particle,
lon=np.zeros(npart),
lat=np.zeros(npart),
x=np.zeros(npart),
y=np.zeros(npart),
)
pset.execute(
pset.Kernel(parcels.kernels.DiffusionUniformKh),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def true_values(

def decaying_moving_example(fieldset, outfile, method=parcels.kernels.AdvectionRK4):
pset = parcels.ParticleSet(
fieldset, pclass=parcels.Particle, lon=start_lon, lat=start_lat
fieldset, pclass=parcels.Particle, x=start_lon, y=start_lat
)

dt = timedelta(minutes=5)
Expand Down
34 changes: 17 additions & 17 deletions docs/user_guide/examples_v3/example_globcurrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_globcurrent_particles():
latstart = [-35]

pset = parcels.ParticleSet(
fieldset, pclass=parcels.Particle, lon=lonstart, lat=latstart
fieldset, pclass=parcels.Particle, x=lonstart, y=latstart
)

pset.execute(
Expand All @@ -91,29 +91,29 @@ def test__particles_init_time():
pset = parcels.ParticleSet(
fieldset,
pclass=parcels.Particle,
lon=lonstart,
lat=latstart,
x=lonstart,
y=latstart,
time=np.datetime64("2002-01-15"),
)
pset2 = parcels.ParticleSet(
fieldset,
pclass=parcels.Particle,
lon=lonstart,
lat=latstart,
x=lonstart,
y=latstart,
time=14 * 86400,
)
pset3 = parcels.ParticleSet(
fieldset,
pclass=parcels.Particle,
lon=lonstart,
lat=latstart,
x=lonstart,
y=latstart,
time=np.array([np.datetime64("2002-01-15")]),
)
pset4 = parcels.ParticleSet(
fieldset,
pclass=parcels.Particle,
lon=lonstart,
lat=latstart,
x=lonstart,
y=latstart,
time=[np.datetime64("2002-01-15")],
)
assert pset[0].time - pset2[0].time == 0
Expand All @@ -126,8 +126,8 @@ def test_globcurrent_outside_time_interval_error():
pset = parcels.ParticleSet(
fieldset,
pclass=parcels.Particle,
lon=[25],
lat=[-35],
x=[25],
y=[-35],
time=fieldset.U.grid.time[0] - timedelta(days=1).total_seconds(),
)
with pytest.raises(parcels.OutsideTimeInterval):
Expand Down Expand Up @@ -173,10 +173,10 @@ def SampleP(particle, fieldset, time): # pragma: no cover
if with_starttime:
time = fieldset.U.grid.time[0] if dt > 0 else fieldset.U.grid.time[-1]
pset = parcels.ParticleSet(
fieldset, pclass=MyParticle, lon=[25], lat=[-35], time=time
fieldset, pclass=MyParticle, x=[25], y=[-35], time=time
)
else:
pset = parcels.ParticleSet(fieldset, pclass=MyParticle, lon=[25], lat=[-35])
pset = parcels.ParticleSet(fieldset, pclass=MyParticle, x=[25], y=[-35])

if with_starttime:
with pytest.raises(parcels.OutsideTimeInterval):
Expand All @@ -202,7 +202,7 @@ def DeleteP0(particle, fieldset, time): # pragma: no cover
particle.delete()

pset0 = parcels.ParticleSet(
fieldset, pclass=parcels.Particle, lon=[25, 25], lat=[-35, -35], time=time0
fieldset, pclass=parcels.Particle, x=[25, 25], y=[-35, -35], time=time0
)

pset0.execute(
Expand All @@ -212,7 +212,7 @@ def DeleteP0(particle, fieldset, time): # pragma: no cover
)

pset1 = parcels.ParticleSet(
fieldset, pclass=parcels.Particle, lon=[25, 25], lat=[-35, -35], time=time0
fieldset, pclass=parcels.Particle, x=[25, 25], y=[-35, -35], time=time0
)

pset1.execute(
Expand All @@ -231,7 +231,7 @@ def test_globcurrent_pset_fromfile(dt, pid_offset, tmpdir):
fieldset = set_globcurrent_fieldset()

parcels.Particle.setLastID(pid_offset)
pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, lon=25, lat=-35)
pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, x=25, y=-35)
pfile = pset.ParticleFile(filename, outputdt=timedelta(hours=6))
pset.execute(
parcels.kernels.AdvectionRK4,
Expand Down Expand Up @@ -265,7 +265,7 @@ def test_error_outputdt_not_multiple_dt(tmpdir):

dt = 81.2584344538292 # number for which output writing fails

pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, lon=[0], lat=[0])
pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, x=[0], y=[0])
ofile = pset.ParticleFile(name=filepath, outputdt=timedelta(days=1))

def DoNothing(particle, fieldset, time): # pragma: no cover
Expand Down
2 changes: 1 addition & 1 deletion docs/user_guide/examples_v3/example_moving_eddies.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def test_moving_eddies_fwdbwd(mesh, tmpdir, npart=2):
lons = [3.3, 3.3] if fieldset.U.grid.mesh == "spherical" else [3.3e5, 3.3e5]
lats = [46.0, 47.8] if fieldset.U.grid.mesh == "spherical" else [1e5, 2.8e5]
pset = parcels.ParticleSet(
fieldset=fieldset, pclass=parcels.Particle, lon=lons, lat=lats
fieldset=fieldset, pclass=parcels.Particle, x=lons, y=lats
)

# Execte for 14 days, with 30sec timesteps and hourly output
Expand Down
8 changes: 4 additions & 4 deletions docs/user_guide/examples_v3/example_ofam.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ def test_ofam_xarray_vs_netcdf(dt):
lonstart, latstart, runtime = (180, 10, timedelta(days=7))

psetN = parcels.ParticleSet(
fieldsetNetcdf, pclass=parcels.Particle, lon=lonstart, lat=latstart
fieldsetNetcdf, pclass=parcels.Particle, x=lonstart, y=latstart
)
psetN.execute(parcels.kernels.AdvectionRK4, runtime=runtime, dt=dt)

psetX = parcels.ParticleSet(
fieldsetxarray, pclass=parcels.Particle, lon=lonstart, lat=latstart
fieldsetxarray, pclass=parcels.Particle, x=lonstart, y=latstart
)
psetX.execute(parcels.kernels.AdvectionRK4, runtime=runtime, dt=dt)

Expand All @@ -74,8 +74,8 @@ def test_ofam_particles(use_xarray):
pset = parcels.ParticleSet(
fieldset,
pclass=parcels.Particle,
lon=lonstart,
lat=latstart,
x=lonstart,
y=latstart,
depth=depstart,
)

Expand Down
1 change: 1 addition & 0 deletions docs/user_guide/v4-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Version 4 of Parcels is unreleased at the moment. The information in this migrat
- Users need to explicitly use `convert_z_to_sigma_croco` in sampling kernels (such as the `AdvectionRK4_3D_CROCO` or `SampleOMegaCroco` kernels) when working with CROCO data, as the automatic conversion from depth to sigma grids under the hood has been removed.
- We added a new AdvectionRK2 Kernel. The AdvectionRK4 kernel is still available, but RK2 is now the recommended default advection scheme as it is faster while the accuracy is comparable for most applications. See also the Choosing an integration method tutorial.
- Functions shouldn't be converted to Kernels before adding to a pset.execute() call. Instead, simply pass the function(s) as a list to pset.execute().
- Kernel variables `lon` and `lat` have been renamed to `x` and `y`, and `dlon` and `dlat` have been renamed to `dx` and `dy`. These changes are also reflected on the ParticleSet as well as the particlefile output.

## FieldSet

Expand Down
2 changes: 1 addition & 1 deletion src/parcels/_compat.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Import helpers for compatability between installations."""


# for compat with v3 of parcels when users provide `initial=attrgetter("lon")` to a Variable
# for compat with v3 of parcels when users provide `initial=attrgetter("...")` to a Variable
# so that particle initial state matches another variable
class _AttrgetterHelper:
"""
Expand Down
6 changes: 3 additions & 3 deletions src/parcels/_core/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def __getitem__(self, key):
self._check_velocitysampling()
try:
if isinstance(key, ParticleSetView):
return self.eval(key.time, key.z, key.lat, key.lon, key)
return self.eval(key.time, key.z, key.y, key.x, key)
else:
return self.eval(*key)
except tuple(AllParcelsErrorCodes.keys()) as error:
Expand Down Expand Up @@ -293,7 +293,7 @@ def eval(self, time: datetime, z, y, x, particles=None):
def __getitem__(self, key):
try:
if isinstance(key, ParticleSetView):
return self.eval(key.time, key.z, key.lat, key.lon, key)
return self.eval(key.time, key.z, key.y, key.x, key)
else:
return self.eval(*key)
except tuple(AllParcelsErrorCodes.keys()) as error:
Expand Down Expand Up @@ -389,7 +389,7 @@ def _assert_same_time_interval(fields: Sequence[Field]) -> None:

def _get_positions(field: Field, time, z, y, x, particles, _ei) -> tuple[dict, dict]:
"""Initialize and populate particle_positions and grid_positions dictionaries"""
particle_positions = {"time": time, "z": z, "lat": y, "lon": x}
particle_positions = {"time": time, "z": z, "y": y, "x": x}
grid_positions = {}
grid_positions.update(_search_time_index(field, time))
grid_positions.update(field.grid.search(z, y, x, ei=_ei))
Expand Down
10 changes: 5 additions & 5 deletions src/parcels/_core/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ def remove_deleted(self, pset):
pset.remove_indices(indices)

def _position_update(self, particles, fieldset):
particles.lon += particles.dlon
particles.lat += particles.dlat
particles.x += particles.dx
particles.y += particles.dy
particles.z += particles.dz
particles.time += particles.dt

particles.dlon = 0
particles.dlat = 0
particles.dx = 0
particles.dy = 0
particles.dz = 0

if hasattr(self.fieldset, "RK45_tol"):
Expand Down Expand Up @@ -244,6 +244,6 @@ def execute(self, pset, endtime, dt):
if error_code == StatusCode.ErrorOutsideTimeInterval:
error_func(pset[inds].time)
else:
error_func(pset[inds].z, pset[inds].lat, pset[inds].lon)
error_func(pset[inds].z, pset[inds].y, pset[inds].x)

return pset
20 changes: 14 additions & 6 deletions src/parcels/_core/particle.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,26 @@ def get_default_particle(spatial_dtype: type[np.float32] | type[np.float64]) ->
attrs={"standard_name": "vertical coordinate", "units": "m", "positive": "down"},
),
Variable(
"lat",
"y",
dtype=spatial_dtype,
attrs={"standard_name": "latitude", "units": "degrees_north", "axis": "Y"},
attrs={
"standard_name": "latitude",
"units": "degrees_north",
"axis": "Y",
}, # TODO v4: Update to ensure that units come from the model
),
Variable(
"lon",
"x",
dtype=spatial_dtype,
attrs={"standard_name": "longitude", "units": "degrees_east", "axis": "X"},
attrs={
"standard_name": "longitude",
"units": "degrees_east",
"axis": "X",
}, # TODO v4: Update to ensure that units come from the model
),
Variable("dz", dtype=spatial_dtype, to_write=False),
Variable("dlat", dtype=spatial_dtype, to_write=False),
Variable("dlon", dtype=spatial_dtype, to_write=False),
Variable("dy", dtype=spatial_dtype, to_write=False),
Variable("dx", dtype=spatial_dtype, to_write=False),
Variable(
"particle_id",
dtype=np.int64,
Expand Down
28 changes: 14 additions & 14 deletions src/parcels/_core/particleset.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def __init__(
pclass=Particle,
time=None,
z=None,
lat=None,
lon=None,
y=None,
x=None,
particle_ids=None,
**kwargs,
):
Expand All @@ -70,21 +70,21 @@ def __init__(

self.fieldset = fieldset
time = np.empty(shape=0) if time is None else np.array(time).flatten()
lat = np.empty(shape=0) if lat is None else np.array(lat).flatten()
lon = np.empty(shape=0) if lon is None else np.array(lon).flatten()
y = np.empty(shape=0) if y is None else np.array(y).flatten()
x = np.empty(shape=0) if x is None else np.array(x).flatten()

if particle_ids is None:
particle_ids = np.arange(lon.size)
particle_ids = np.arange(x.size)

if z is None:
minz = 0
for field in self.fieldset.fields.values():
if field.grid.depth is not None:
minz = min(minz, field.grid.depth[0])
z = np.ones(lon.size) * minz
z = np.ones(x.size) * minz
else:
z = np.array(z).flatten()
assert lon.size == lat.size and lon.size == z.size, "lon, lat, z don't all have the same lenghts"
assert x.size == y.size and x.size == z.size, "lon, lat, z don't all have the same lenghts"

if time is None or len(time) == 0:
# do not set a time yet (because sign_dt not known)
Expand All @@ -95,26 +95,26 @@ def __init__(
time = timedelta_to_float(time)
else:
raise TypeError("particle time must be a datetime, timedelta, or date object")
time = np.repeat(time, lon.size) if time.size == 1 else time
time = np.repeat(time, x.size) if time.size == 1 else time

assert lon.size == time.size, "time and positions (lon, lat, z) do not have the same lengths."
assert x.size == time.size, "time and positions (lon, lat, z) do not have the same lengths."

if fieldset.time_interval:
_warn_particle_times_outside_fieldset_time_bounds(time, fieldset.time_interval)

for kwvar in kwargs:
kwargs[kwvar] = np.array(kwargs[kwvar]).flatten()
assert lon.size == kwargs[kwvar].size, f"{kwvar} and positions (lon, lat, z) don't have the same lengths."
assert x.size == kwargs[kwvar].size, f"{kwvar} and positions (lon, lat, z) don't have the same lengths."

self._data = create_particle_data(
pclass=pclass,
nparticles=lon.size,
nparticles=x.size,
ngrids=len(fieldset.gridset),
initial=dict(
time=time,
z=z,
lat=lat,
lon=lon,
y=y,
x=x,
particle_id=particle_ids,
),
)
Expand Down Expand Up @@ -245,7 +245,7 @@ def remove_indices(self, indices):
def populate_indices(self):
"""Pre-populate guesses of particle ei (element id) indices"""
for i, grid in enumerate(self.fieldset.gridset):
grid_positions = grid.search(self.z, self.lat, self.lon)
grid_positions = grid.search(self.z, self.y, self.x)
self._data["ei"][:, i] = grid.ravel_index(
{
"X": grid_positions["X"]["index"],
Expand Down
10 changes: 5 additions & 5 deletions src/parcels/_core/statuscodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class FieldInterpolationError(RuntimeError):


def _raise_field_interpolation_error(z, y, x):
raise FieldInterpolationError(f"Field interpolation returned NaN at (z={z}, lat={y}, lon={x})")
raise FieldInterpolationError(f"Field interpolation returned NaN at (z={z}, y={y}, x={x})")


class FieldOutOfBoundError(RuntimeError):
Expand All @@ -51,7 +51,7 @@ class FieldOutOfBoundError(RuntimeError):


def _raise_field_out_of_bound_error(z, y, x):
raise FieldOutOfBoundError(f"Field sampled out-of-bound, at (z={z}, lat={y}, lon={x})")
raise FieldOutOfBoundError(f"Field sampled out-of-bound, at (z={z}, y={y}, x={x})")


class FieldOutOfBoundSurfaceError(RuntimeError):
Expand All @@ -65,7 +65,7 @@ def format_out(val):
return "unknown" if val is None else val

raise FieldOutOfBoundSurfaceError(
f"Field sampled out-of-bound at the surface, at (z={format_out(z)}, lat={format_out(y)}, lon={format_out(x)})"
f"Field sampled out-of-bound at the surface, at (z={format_out(z)}, y={format_out(y)}, x={format_out(x)})"
)


Expand All @@ -82,7 +82,7 @@ class GridSearchingError(RuntimeError):


def _raise_grid_searching_error(z, y, x):
raise GridSearchingError(f"Grid searching failed at (z={z}, lat={y}, lon={x})")
raise GridSearchingError(f"Grid searching failed at (z={z}, y={y}, x={x})")


class GeneralError(RuntimeError):
Expand All @@ -92,7 +92,7 @@ class GeneralError(RuntimeError):


def _raise_general_error(z, y, x):
raise GeneralError(f"General error occurred at (z={z}, lat={y}, lon={x})")
raise GeneralError(f"General error occurred at (z={z}, y={y}, x={x})")


class OutsideTimeInterval(RuntimeError):
Expand Down
Loading