Skip to content

Commit f46b2be

Browse files
Merge pull request #1810 from OceanParcels/warn_particle_times_outside_fieldset_time_bounds
Implement warning for particles initialised outside time domain
2 parents 70e26eb + baa5f91 commit f46b2be

File tree

5 files changed

+54
-12
lines changed

5 files changed

+54
-12
lines changed

parcels/particleset.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from parcels._compat import MPI
1515
from parcels.application_kernels.advection import AdvectionRK4
1616
from parcels.compilation.codecompiler import GNUCompiler
17-
from parcels.field import NestedField
17+
from parcels.field import Field, NestedField
1818
from parcels.grid import CurvilinearGrid, GridType
1919
from parcels.interaction.interactionkernel import InteractionKernel
2020
from parcels.interaction.neighborsearch import (
@@ -32,7 +32,7 @@
3232
from parcels.tools.global_statics import get_package_dir
3333
from parcels.tools.loggers import logger
3434
from parcels.tools.statuscodes import StatusCode
35-
from parcels.tools.warnings import FileWarning
35+
from parcels.tools.warnings import ParticleSetWarning
3636

3737
__all__ = ["ParticleSet"]
3838

@@ -174,6 +174,8 @@ def ArrayClass_init(self, *args, **kwargs):
174174
raise NotImplementedError("If fieldset.time_origin is not a date, time of a particle must be a double")
175175
time = np.array([self.time_origin.reltime(t) if _convert_to_reltime(t) else t for t in time])
176176
assert lon.size == time.size, "time and positions (lon, lat, depth) do not have the same lengths."
177+
if isinstance(fieldset.U, Field) and (not fieldset.U.allow_time_extrapolation):
178+
_warn_particle_times_outside_fieldset_time_bounds(time, fieldset.U.grid.time_full)
177179

178180
if lonlatdepth_dtype is None:
179181
lonlatdepth_dtype = self.lonlatdepth_dtype_from_field_interp_method(fieldset.U)
@@ -792,7 +794,7 @@ def from_particlefile(
792794
f"Note that the `repeatdt` argument is not retained from {filename}, and that "
793795
"setting a new repeatdt will start particles from the _new_ particle "
794796
"locations.",
795-
FileWarning,
797+
ParticleSetWarning,
796798
stacklevel=2,
797799
)
798800

@@ -1247,6 +1249,22 @@ def _warn_outputdt_release_desync(outputdt: float, starttime: float, release_tim
12471249
"Some of the particles have a start time difference that is not a multiple of outputdt. "
12481250
"This could cause the first output of some of the particles that start later "
12491251
"in the simulation to be at a different time than expected.",
1250-
FileWarning,
1252+
ParticleSetWarning,
12511253
stacklevel=2,
12521254
)
1255+
1256+
1257+
def _warn_particle_times_outside_fieldset_time_bounds(release_times: np.ndarray, time_full: np.ndarray):
1258+
if np.any(release_times):
1259+
if np.any(release_times < time_full[0]):
1260+
warnings.warn(
1261+
"Some particles are set to be released before the fieldset's first time and allow_time_extrapolation is set to False.",
1262+
ParticleSetWarning,
1263+
stacklevel=2,
1264+
)
1265+
if np.any(release_times > time_full[-1]):
1266+
warnings.warn(
1267+
"Some particles are set to be released after the fieldset's last time and allow_time_extrapolation is set to False.",
1268+
ParticleSetWarning,
1269+
stacklevel=2,
1270+
)

parcels/tools/warnings.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import warnings
22

3-
__all__ = ["FieldSetWarning", "FileWarning", "KernelWarning"]
3+
__all__ = ["FieldSetWarning", "FileWarning", "KernelWarning", "ParticleSetWarning"]
44

55

66
class FieldSetWarning(UserWarning):
@@ -13,6 +13,12 @@ class FieldSetWarning(UserWarning):
1313
pass
1414

1515

16+
class ParticleSetWarning(UserWarning):
17+
"""Warning that is raised when there are issues in the construction of the ParticleSet."""
18+
19+
pass
20+
21+
1622
class FileWarning(UserWarning):
1723
"""Warning that is raised when there are issues with input or output files.
1824

tests/test_particlesets.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
FieldSet,
88
JITParticle,
99
ParticleSet,
10+
ParticleSetWarning,
1011
ScipyParticle,
1112
StatusCode,
1213
Variable,
@@ -175,6 +176,14 @@ def test_pset_create_with_time(fieldset, mode):
175176
assert np.allclose([p.time for p in pset], time, rtol=1e-12)
176177

177178

179+
@pytest.mark.parametrize("mode", ["scipy", "jit"])
180+
def test_pset_create_outside_time(mode):
181+
fieldset = create_fieldset_zeros_simple(withtime=True)
182+
time = [-1, 0, 1, 20 * 86400]
183+
with pytest.warns(ParticleSetWarning, match="Some particles are set to be released*"):
184+
ParticleSet(fieldset, pclass=ptype[mode], lon=[0] * len(time), lat=[0] * len(time), time=time)
185+
186+
178187
@pytest.mark.parametrize("mode", ["scipy", "jit"])
179188
def test_pset_not_multipldt_time(fieldset, mode):
180189
times = [0, 1.1]

tests/tools/test_warnings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
AdvectionRK45,
1010
FieldSet,
1111
FieldSetWarning,
12-
FileWarning,
1312
KernelWarning,
1413
ParticleSet,
14+
ParticleSetWarning,
1515
ScipyParticle,
1616
)
1717
from tests.utils import TEST_DATA
@@ -62,7 +62,7 @@ def test_file_warnings(tmp_zarrfile):
6262
)
6363
pset = ParticleSet(fieldset=fieldset, pclass=ScipyParticle, lon=[0, 0], lat=[0, 0], time=[0, 1])
6464
pfile = pset.ParticleFile(name=tmp_zarrfile, outputdt=2)
65-
with pytest.warns(FileWarning, match="Some of the particles have a start time difference.*"):
65+
with pytest.warns(ParticleSetWarning, match="Some of the particles have a start time difference.*"):
6666
pset.execute(AdvectionRK4, runtime=3, dt=1, output_file=pfile)
6767

6868

tests/utils.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,24 @@ def create_flat_positions(n_particle):
7272
return np.random.rand(n_particle * 3).reshape(3, n_particle)
7373

7474

75-
def create_fieldset_zeros_simple(xdim=40, ydim=100):
76-
U = np.zeros((ydim, xdim), dtype=np.float32)
77-
V = np.zeros((ydim, xdim), dtype=np.float32)
75+
def create_fieldset_zeros_simple(xdim=40, ydim=100, withtime=False):
7876
lon = np.linspace(0, 1, xdim, dtype=np.float32)
7977
lat = np.linspace(-60, 60, ydim, dtype=np.float32)
8078
depth = np.zeros(1, dtype=np.float32)
81-
data = {"U": np.array(U, dtype=np.float32), "V": np.array(V, dtype=np.float32)}
8279
dimensions = {"lat": lat, "lon": lon, "depth": depth}
83-
return FieldSet.from_data(data, dimensions)
80+
if withtime:
81+
tdim = 10
82+
time = np.linspace(0, 86400, tdim, dtype=np.float64)
83+
dimensions["time"] = time
84+
datadims = (tdim, ydim, xdim)
85+
allow_time_extrapolation = False
86+
else:
87+
datadims = (ydim, xdim)
88+
allow_time_extrapolation = True
89+
U = np.zeros(datadims, dtype=np.float32)
90+
V = np.zeros(datadims, dtype=np.float32)
91+
data = {"U": np.array(U, dtype=np.float32), "V": np.array(V, dtype=np.float32)}
92+
return FieldSet.from_data(data, dimensions, allow_time_extrapolation=allow_time_extrapolation)
8493

8594

8695
def assert_empty_folder(path: Path):

0 commit comments

Comments
 (0)