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: 6 additions & 0 deletions src/parcels/_core/particlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ def close(self):
self._writer.close()
self._writer = None

def __enter__(self):
return self

def __exit__(self, *args):
self.close()


def _get_vars_to_write(particle: ParticleClass) -> list[Variable]:
return [v for v in particle.variables if v.to_write is not False]
Expand Down
50 changes: 25 additions & 25 deletions src/parcels/_core/particleset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import types
import warnings
from collections.abc import Iterable
from contextlib import nullcontext
from typing import Literal

import numpy as np
Expand Down Expand Up @@ -433,32 +434,31 @@ def execute(
next_output = start_time + outputdt * sign_dt

time = start_time
while sign_dt * (time - end_time) < 0:
if next_output is not None:
f = min if sign_dt > 0 else max
next_time = f(next_output, end_time)
else:
next_time = end_time

self._kernel.execute(self, endtime=next_time, dt=dt)

if next_output is not None:
if np.abs(next_time - next_output) < 0.001:
if output_file:
output_file.write(self, next_output)
if np.isfinite(outputdt):
next_output += outputdt * sign_dt

if verbose_progress:
pbar.set_description_str(
"Integration time: " + str(float_to_datelike(time, self.fieldset.time_interval))
)
pbar.update(sign_dt * (next_time - time))

time = next_time

if output_file is not None:
output_file.close()
with output_file if output_file is not None else nullcontext():
while sign_dt * (time - end_time) < 0:
if next_output is not None:
f = min if sign_dt > 0 else max
next_time = f(next_output, end_time)
else:
next_time = end_time

self._kernel.execute(self, endtime=next_time, dt=dt)

if next_output is not None:
if np.abs(next_time - next_output) < 0.001:
if output_file:
output_file.write(self, next_output)
if np.isfinite(outputdt):
next_output += outputdt * sign_dt

if verbose_progress:
pbar.set_description_str(
"Integration time: " + str(float_to_datelike(time, self.fieldset.time_interval))
)
pbar.update(sign_dt * (next_time - time))

time = next_time
Comment on lines -436 to +461

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only difference with this block is the indentation and the with statement


if verbose_progress:
pbar.close()
Expand Down
17 changes: 17 additions & 0 deletions tests/test_particlefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,23 @@ def test_pfile_write_custom_particle():
...


def test_particlefile_readable_after_kernel_error(fieldset, tmp_parquet):
"""Parquet output file must be readable even if the kernel raises an error mid-execution (GH-2713)."""

def ErrorKernel(particles, fieldset): # pragma: no cover
particles.state = StatusCode.Error

pset = ParticleSet(fieldset, pclass=Particle, x=0, y=0)
ofile = ParticleFile(tmp_parquet, outputdt=np.timedelta64(1, "s"))

with pytest.raises(RuntimeError, match="General error occurred at"):
pset.execute(ErrorKernel, runtime=np.timedelta64(10, "s"), dt=np.timedelta64(1, "s"), output_file=ofile)

# File must be readable despite the crash
df = pd.read_parquet(tmp_parquet)
assert len(df) >= 1 # at least the initial condition was written


@pytest.mark.xfail(
reason="set_variable_write_status should be removed - with Particle writing defined on the particle level. GH2186"
)
Expand Down