From 5468434da33306eff2ff98c045ea92ff8b68257a Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:22:36 +0200 Subject: [PATCH 01/16] Update Variable repr --- parcels/particle.py | 2 +- tests/test_reprs.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/parcels/particle.py b/parcels/particle.py index a8f5ead1f..728e0deaa 100644 --- a/parcels/particle.py +++ b/parcels/particle.py @@ -53,7 +53,7 @@ def __set__(self, instance, value): setattr(instance, f"_{self.name}", value) def __repr__(self): - return f"PVar<{self.name}|{self.dtype}>" + return f"Variable(name={self._name}, dtype={self.dtype}, initial={self.initial}, to_write={self.to_write})" def is64bit(self): """Check whether variable is 64-bit.""" diff --git a/tests/test_reprs.py b/tests/test_reprs.py index d072ded9f..08854cfd6 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -2,7 +2,7 @@ import numpy as np -from parcels import Grid, TimeConverter +from parcels import Grid, TimeConverter, Variable from parcels.grid import RectilinearGrid @@ -25,6 +25,12 @@ def test_grid_repr(): validate_simple_repr(Grid, kwargs) +def test_variable_repr(): + """Test arguments are in the repr of the Variable object.""" + kwargs = dict(name="test", dtype=np.float32, initial=0, to_write=False) + validate_simple_repr(Variable, kwargs) + + def test_rectilineargrid_repr(): """ Test arguments are in the repr of a RectilinearGrid object. From f3eda291df84057938bac9c4ac82a7c4fb7440f5 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:27:15 +0200 Subject: [PATCH 02/16] Add ParticleFile and Field reprs #1693 --- parcels/field.py | 5 ++++- parcels/particlefile.py | 12 +++++++++- parcels/tools/_helpers.py | 22 +++++++++++++++++++ tests/test_reprs.py | 46 ++++++++++++++++++++++++++++++++++++++- tests/utils.py | 7 ++++++ 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/parcels/field.py b/parcels/field.py index ed907747a..348d63555 100644 --- a/parcels/field.py +++ b/parcels/field.py @@ -21,7 +21,7 @@ assert_valid_gridindexingtype, assert_valid_interp_method, ) -from parcels.tools._helpers import deprecated_made_private +from parcels.tools._helpers import deprecated_made_private, pretty_field from parcels.tools.converters import ( Geographic, GeographicPolar, @@ -312,6 +312,9 @@ def __init__( if len(kwargs) > 0: raise SyntaxError(f'Field received an unexpected keyword argument "{list(kwargs.keys())[0]}"') + def __repr__(self) -> str: + return pretty_field(self) + @property @deprecated_made_private # TODO: Remove 6 months after v3.1.0 def dataFiles(self): diff --git a/parcels/particlefile.py b/parcels/particlefile.py index 31011aadf..bf2226d47 100644 --- a/parcels/particlefile.py +++ b/parcels/particlefile.py @@ -10,7 +10,7 @@ import parcels from parcels._compat import MPI -from parcels.tools._helpers import deprecated, deprecated_made_private +from parcels.tools._helpers import default_repr, deprecated, deprecated_made_private from parcels.tools.warnings import FileWarning __all__ = ["ParticleFile"] @@ -116,6 +116,16 @@ def __init__(self, name, particleset, outputdt=np.inf, chunks=None, create_new_z fname = name if extension in [".zarr"] else f"{name}.zarr" self._fname = fname + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(" + f"name={self.fname!r}, " + f"particleset={default_repr(self.particleset)}, " + f"outputdt={self.outputdt!r}, " + f"chunks={self.chunks!r}, " + f"create_new_zarrfile={self.create_new_zarrfile!r})" + ) + @property def create_new_zarrfile(self): return self._create_new_zarrfile diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index 381338f0e..f96f82fb4 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -1,9 +1,15 @@ """Internal helpers for Parcels.""" +from __future__ import annotations + import functools import warnings from collections.abc import Callable +from textwrap import dedent +from typing import TYPE_CHECKING, Any +if TYPE_CHECKING: + from parcels import Field PACKAGE = "Parcels" @@ -56,3 +62,19 @@ def deprecated_made_private(func: Callable) -> Callable: def patch_docstring(obj: Callable, extra: str) -> None: obj.__doc__ = f"{obj.__doc__ or ''}{extra}".strip() + + +def pretty_field(field: Field) -> str: + """Return a pretty repr for Field""" + out = f"""<{type(field).__name__}> + grid : {field.grid!r } + extrapolate time: {field.allow_time_extrapolation!r} + time_periodic : {field.time_periodic!r } + gridindexingtype: {field.gridindexingtype!r } + to_write : {field.to_write!r } + """ + return dedent(out) + + +def default_repr(obj: Any): + return object.__repr__(obj) diff --git a/tests/test_reprs.py b/tests/test_reprs.py index 08854cfd6..52c8f79f5 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -1,9 +1,12 @@ +import re +from datetime import timedelta from typing import Any import numpy as np -from parcels import Grid, TimeConverter, Variable +from parcels import Grid, ParticleFile, TimeConverter, Variable from parcels.grid import RectilinearGrid +from tests.utils import create_fieldset_unit_mesh, create_simple_pset def validate_simple_repr(class_: type, kwargs: dict[str, Any]): @@ -17,6 +20,33 @@ def validate_simple_repr(class_: type, kwargs: dict[str, Any]): assert class_.__name__ in obj_repr +def valid_indentation(str) -> bool: + """Make sure that all lines in string is indented with a multiple of 4 spaces.""" + lines = str.split("\n") + for line in lines: + line = re.sub("^( {4})+", "", line) + if line.startswith(" "): + return False + return True + + +def test_check_indentation(): + valid = """ +test + test +test + test + test + test""" + assert valid_indentation(valid) + invalid = """ +test + test + invalid! +""" + assert not valid_indentation(invalid) + + def test_grid_repr(): """Test arguments are in the repr of a Grid object""" kwargs = dict( @@ -41,3 +71,17 @@ def test_rectilineargrid_repr(): lon=np.array([1, 2, 3]), lat=np.array([4, 5, 6]), time=None, time_origin=TimeConverter(), mesh="spherical" ) validate_simple_repr(RectilinearGrid, kwargs) + + +def test_particlefile_repr(): + pset = create_simple_pset() + kwargs = dict( + name="file.zarr", particleset=pset, outputdt=timedelta(hours=1), chunks=None, create_new_zarrfile=False + ) + validate_simple_repr(ParticleFile, kwargs) + + +def test_field_repr(): + """Simply that no errors arise""" + field = create_fieldset_unit_mesh().U + assert valid_indentation(repr(field)) diff --git a/tests/utils.py b/tests/utils.py index a5153f6be..1c4cab8d4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,6 +4,7 @@ import numpy as np +import parcels from parcels import FieldSet PROJECT_ROOT = Path(__file__).resolve().parents[1] @@ -47,6 +48,12 @@ def create_fieldset_zeros_conversion(mesh="spherical", xdim=200, ydim=100, mesh_ return FieldSet.from_data(data, dimensions, mesh=mesh) +def create_simple_pset(): + return parcels.ParticleSet( + fieldset=create_fieldset_unit_mesh(), pclass=parcels.ScipyParticle, lon=[0], lat=[0], depth=[0], time=[0] + ) + + def create_spherical_positions(n_particles, max_depth=100000): yrange = 2 * np.random.rand(n_particles) lat = 180 * (np.arccos(1 - yrange) - 0.5 * np.pi) / np.pi From 7ad03acc87a6bd930e29f03e0ae3c9fb433e01c6 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:36:20 +0200 Subject: [PATCH 03/16] Add pretty ParticleSet repr --- parcels/particleset.py | 4 ++-- parcels/tools/_helpers.py | 18 +++++++++++++++++- tests/test_reprs.py | 13 ++++++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/parcels/particleset.py b/parcels/particleset.py index e6c819c69..847a1025b 100644 --- a/parcels/particleset.py +++ b/parcels/particleset.py @@ -27,7 +27,7 @@ from parcels.particle import JITParticle, Variable from parcels.particledata import ParticleData, ParticleDataIterator from parcels.particlefile import ParticleFile -from parcels.tools._helpers import deprecated, deprecated_made_private +from parcels.tools._helpers import deprecated, deprecated_made_private, pretty_particleset from parcels.tools.converters import _get_cftime_calendars, convert_to_flat_array from parcels.tools.global_statics import get_package_dir from parcels.tools.loggers import logger @@ -386,7 +386,7 @@ def size(self): return len(self.particledata) def __repr__(self): - return "\n".join([str(p) for p in self]) + return pretty_particleset(self) def __len__(self): return len(self.particledata) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index f96f82fb4..983627b28 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from parcels import Field + from parcels import Field, ParticleSet PACKAGE = "Parcels" @@ -76,5 +76,21 @@ def pretty_field(field: Field) -> str: return dedent(out) +def pretty_particleset(pset: ParticleSet) -> str: + """Return a pretty repr for ParticleSet""" + if len(pset) < 10: + lst = [repr(p) for p in pset] + else: + lst = [repr(p) for p in pset[:7]] + ["..."] + + out = f"""<{type(pset).__name__}> + fieldset: {pset.fieldset} + pclass: {pset.pclass} + repeatdt: {pset.repeatdt} + # particles: {len(pset)} + particles: {lst}""" + return dedent(out) + + def default_repr(obj: Any): return object.__repr__(obj) diff --git a/tests/test_reprs.py b/tests/test_reprs.py index 52c8f79f5..10652ea0d 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -20,9 +20,12 @@ def validate_simple_repr(class_: type, kwargs: dict[str, Any]): assert class_.__name__ in obj_repr -def valid_indentation(str) -> bool: +def valid_indentation(s: str) -> bool: """Make sure that all lines in string is indented with a multiple of 4 spaces.""" - lines = str.split("\n") + if s.startswith(" "): + return False + + lines = s.split("\n") for line in lines: line = re.sub("^( {4})+", "", line) if line.startswith(" "): @@ -82,6 +85,10 @@ def test_particlefile_repr(): def test_field_repr(): - """Simply that no errors arise""" field = create_fieldset_unit_mesh().U assert valid_indentation(repr(field)) + + +def test_particleset_repr(): + pset = create_simple_pset() + valid_indentation(repr(pset)) From d8de9bcb865bbe10281a9a805becd06d7338a0de Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:30:55 +0200 Subject: [PATCH 04/16] fix-indentation --- parcels/tools/_helpers.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index 983627b28..6a09e42c8 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -67,29 +67,30 @@ def patch_docstring(obj: Callable, extra: str) -> None: def pretty_field(field: Field) -> str: """Return a pretty repr for Field""" out = f"""<{type(field).__name__}> - grid : {field.grid!r } - extrapolate time: {field.allow_time_extrapolation!r} - time_periodic : {field.time_periodic!r } - gridindexingtype: {field.gridindexingtype!r } - to_write : {field.to_write!r } - """ - return dedent(out) + grid : {field.grid!r } + extrapolate time: {field.allow_time_extrapolation!r} + time_periodic : {field.time_periodic!r } + gridindexingtype: {field.gridindexingtype!r } + to_write : {field.to_write!r } +""" + return dedent(out).strip() def pretty_particleset(pset: ParticleSet) -> str: """Return a pretty repr for ParticleSet""" if len(pset) < 10: - lst = [repr(p) for p in pset] + lst = list(pset) else: - lst = [repr(p) for p in pset[:7]] + ["..."] + lst = [p for p in pset[:7]] + ["..."] out = f"""<{type(pset).__name__}> - fieldset: {pset.fieldset} - pclass: {pset.pclass} - repeatdt: {pset.repeatdt} - # particles: {len(pset)} - particles: {lst}""" - return dedent(out) + fieldset : {pset.fieldset} + pclass : {pset.pclass} + repeatdt : {pset.repeatdt} + # particles: {len(pset)} + particles : {lst!r} +""" + return dedent(out).strip() def default_repr(obj: Any): From b972d0116a3523e788a12eb45b93c4174731d98c Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:10:25 +0200 Subject: [PATCH 05/16] Patch pset indexing to avoid slicing --- parcels/tools/_helpers.py | 2 +- tests/test_reprs.py | 3 +++ tests/utils.py | 10 ++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index 6a09e42c8..a482befdb 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -81,7 +81,7 @@ def pretty_particleset(pset: ParticleSet) -> str: if len(pset) < 10: lst = list(pset) else: - lst = [p for p in pset[:7]] + ["..."] + lst = [pset[i] for i in range(7)] + ["..."] out = f"""<{type(pset).__name__}> fieldset : {pset.fieldset} diff --git a/tests/test_reprs.py b/tests/test_reprs.py index 10652ea0d..593c26cd6 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -92,3 +92,6 @@ def test_field_repr(): def test_particleset_repr(): pset = create_simple_pset() valid_indentation(repr(pset)) + + pset = create_simple_pset(n=15) + valid_indentation(repr(pset)) diff --git a/tests/utils.py b/tests/utils.py index 1c4cab8d4..14ef8b7e5 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -48,9 +48,15 @@ def create_fieldset_zeros_conversion(mesh="spherical", xdim=200, ydim=100, mesh_ return FieldSet.from_data(data, dimensions, mesh=mesh) -def create_simple_pset(): +def create_simple_pset(n=1): + zeros = np.zeros(n) return parcels.ParticleSet( - fieldset=create_fieldset_unit_mesh(), pclass=parcels.ScipyParticle, lon=[0], lat=[0], depth=[0], time=[0] + fieldset=create_fieldset_unit_mesh(), + pclass=parcels.ScipyParticle, + lon=zeros, + lat=zeros, + depth=zeros, + time=zeros, ) From fd7db3a9f32dcbed4d27fe5910ba2ab0a6775dc0 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:19:58 +0200 Subject: [PATCH 06/16] Update pset particles repr formatting --- parcels/tools/_helpers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index a482befdb..a00a72431 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -79,16 +79,17 @@ def pretty_field(field: Field) -> str: def pretty_particleset(pset: ParticleSet) -> str: """Return a pretty repr for ParticleSet""" if len(pset) < 10: - lst = list(pset) + particles = repr(list(pset)) else: - lst = [pset[i] for i in range(7)] + ["..."] + lst = [repr(pset[i]) for i in range(7)] + ["..."] + particles = f"[{', '.join(lst)}]" out = f"""<{type(pset).__name__}> fieldset : {pset.fieldset} pclass : {pset.pclass} repeatdt : {pset.repeatdt} # particles: {len(pset)} - particles : {lst!r} + particles : {particles} """ return dedent(out).strip() From b0fe8853eaa56e9edb8de71c3eddbc0d4d870c6e Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 25 Oct 2024 11:58:14 +0200 Subject: [PATCH 07/16] Adding new print statements to parcels tutorial --- docs/examples/parcels_tutorial.ipynb | 34 +++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/examples/parcels_tutorial.ipynb b/docs/examples/parcels_tutorial.ipynb index 5936644aa..2f11c60f4 100644 --- a/docs/examples/parcels_tutorial.ipynb +++ b/docs/examples/parcels_tutorial.ipynb @@ -152,8 +152,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "P[0](lon=330000.000000, lat=100000.000000, depth=0.000000, time=not_yet_set)\n", - "P[1](lon=330000.000000, lat=280000.000000, depth=0.000000, time=not_yet_set)\n" + "\n", + " fieldset : \n", + " pclass : False\n", + " repeatdt : None\n", + " # particles: 2\n", + " particles : [P[0](lon=330000.000000, lat=100000.000000, depth=0.000000, time=not_yet_set), P[1](lon=330000.000000, lat=280000.000000, depth=0.000000, time=not_yet_set)]\n" ] } ], @@ -220,8 +224,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "INFO: Output files are stored in EddyParticles.zarr.\n", - "100%|██████████| 518400.0/518400.0 [00:03<00:00, 172443.78it/s]\n" + "INFO: Output files are stored in EddyParticles.zarr.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/erik/Codes/ParcelsCode/parcels/particleset.py:1082: FileWarning: Some of the particles have a start time that is not a multiple of outputdt. This could cause the first output to be at a different time than expected.\n", + " _warn_outputdt_release_desync(outputdt, self.particledata.data[\"time_nextloop\"])\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100%|██████████| 518400.0/518400.0 [00:05<00:00, 95421.55it/s] \n" ] } ], @@ -13228,8 +13246,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "P[0](lon=329983.281250, lat=100495.609375, depth=0.000000, time=300.000000)\n", - "P[1](lon=330289.968750, lat=280418.906250, depth=0.000000, time=300.000000)\n" + "\n", + " fieldset : \n", + " pclass : False\n", + " repeatdt : None\n", + " # particles: 2\n", + " particles : [P[0](lon=329983.281250, lat=100495.609375, depth=0.000000, time=300.000000), P[1](lon=330289.968750, lat=280418.906250, depth=0.000000, time=300.000000)]\n" ] }, { From 7db3d629d9aa1d60d19d585caa440239c2215743 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:05:42 +0100 Subject: [PATCH 08/16] Update reprs and add FieldSet repr Multiline particle printing Update function naming FieldSet repr --- parcels/field.py | 4 +-- parcels/fieldset.py | 5 ++- parcels/particleset.py | 4 +-- parcels/tools/_helpers.py | 65 +++++++++++++++++++++++++++++-------- tests/test_reprs.py | 15 ++++++--- tests/tools/test_helpers.py | 11 +++++++ 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/parcels/field.py b/parcels/field.py index 348d63555..ac9772926 100644 --- a/parcels/field.py +++ b/parcels/field.py @@ -21,7 +21,7 @@ assert_valid_gridindexingtype, assert_valid_interp_method, ) -from parcels.tools._helpers import deprecated_made_private, pretty_field +from parcels.tools._helpers import deprecated_made_private, field_repr from parcels.tools.converters import ( Geographic, GeographicPolar, @@ -313,7 +313,7 @@ def __init__( raise SyntaxError(f'Field received an unexpected keyword argument "{list(kwargs.keys())[0]}"') def __repr__(self) -> str: - return pretty_field(self) + return field_repr(self) @property @deprecated_made_private # TODO: Remove 6 months after v3.1.0 diff --git a/parcels/fieldset.py b/parcels/fieldset.py index e62ef45a8..4f01f291c 100644 --- a/parcels/fieldset.py +++ b/parcels/fieldset.py @@ -14,7 +14,7 @@ from parcels.grid import Grid from parcels.gridset import GridSet from parcels.particlefile import ParticleFile -from parcels.tools._helpers import deprecated_made_private +from parcels.tools._helpers import deprecated_made_private, fieldset_repr from parcels.tools.converters import TimeConverter, convert_xarray_time_units from parcels.tools.loggers import logger from parcels.tools.statuscodes import TimeExtrapolationError @@ -56,6 +56,9 @@ def __init__(self, U: Field | NestedField | None, V: Field | NestedField | None, self.compute_on_defer = None self._add_UVfield() + def __repr__(self): + return fieldset_repr(self) + @property def particlefile(self): return self._particlefile diff --git a/parcels/particleset.py b/parcels/particleset.py index 847a1025b..d83de4686 100644 --- a/parcels/particleset.py +++ b/parcels/particleset.py @@ -27,7 +27,7 @@ from parcels.particle import JITParticle, Variable from parcels.particledata import ParticleData, ParticleDataIterator from parcels.particlefile import ParticleFile -from parcels.tools._helpers import deprecated, deprecated_made_private, pretty_particleset +from parcels.tools._helpers import deprecated, deprecated_made_private, particleset_repr from parcels.tools.converters import _get_cftime_calendars, convert_to_flat_array from parcels.tools.global_statics import get_package_dir from parcels.tools.loggers import logger @@ -386,7 +386,7 @@ def size(self): return len(self.particledata) def __repr__(self): - return pretty_particleset(self) + return particleset_repr(self) def __len__(self): return len(self.particledata) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index a00a72431..56f2ee490 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -3,13 +3,14 @@ from __future__ import annotations import functools +import textwrap import warnings from collections.abc import Callable -from textwrap import dedent from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from parcels import Field, ParticleSet + from parcels import Field, FieldSet, ParticleSet + PACKAGE = "Parcels" @@ -64,34 +65,70 @@ def patch_docstring(obj: Callable, extra: str) -> None: obj.__doc__ = f"{obj.__doc__ or ''}{extra}".strip() -def pretty_field(field: Field) -> str: +def field_repr(field: Field) -> str: """Return a pretty repr for Field""" out = f"""<{type(field).__name__}> - grid : {field.grid!r } + grid : {field.grid!r} extrapolate time: {field.allow_time_extrapolation!r} - time_periodic : {field.time_periodic!r } - gridindexingtype: {field.gridindexingtype!r } - to_write : {field.to_write!r } + time_periodic : {field.time_periodic!r} + gridindexingtype: {field.gridindexingtype!r} + to_write : {field.to_write!r} """ - return dedent(out).strip() + return textwrap.dedent(out).strip() + + +def _format_list_items_multiline(items: list[str], level: int = 1) -> str: + """Given a list of strings, formats them across multiple lines. + + Uses indentation levels of 4 spaces provided by ``level``. + + Example + ------- + >>> output = _format_list_items_multiline(["item1", "item2", "item3"], 4) + >>> f"my_items: {output}" + my_items: [ + item1, + item2, + item3, + ] + """ + if len(items) == 0: + return "[]" + assert level >= 1, "Indentation level >=1 supported" + indentation_str = level * 4 * " " + indentation_str_end = (level - 1) * 4 * " " -def pretty_particleset(pset: ParticleSet) -> str: + items = ",\n".join([textwrap.indent(i, indentation_str) for i in items]) + return f"[\n{items}\n{indentation_str_end}]" + + +def particleset_repr(pset: ParticleSet) -> str: """Return a pretty repr for ParticleSet""" if len(pset) < 10: - particles = repr(list(pset)) + particles = [repr(p for p in pset)] else: - lst = [repr(pset[i]) for i in range(7)] + ["..."] - particles = f"[{', '.join(lst)}]" + particles = [repr(pset[i]) for i in range(7)] + ["..."] out = f"""<{type(pset).__name__}> fieldset : {pset.fieldset} pclass : {pset.pclass} repeatdt : {pset.repeatdt} # particles: {len(pset)} - particles : {particles} + particles : {_format_list_items_multiline(particles)} +""" + return textwrap.dedent(out).strip() + + +def fieldset_repr(fieldset: FieldSet) -> str: + """Return a pretty repr for FieldSet""" + fields_repr = "\n".join([repr(f) for f in fieldset.get_fields()]) + + out = f"""<{type(fieldset).__name__}> + fields: +{textwrap.indent(fields_repr, 8 * " ")} """ - return dedent(out).strip() + return textwrap.dedent(out).strip() def default_repr(obj: Any): diff --git a/tests/test_reprs.py b/tests/test_reprs.py index 593c26cd6..32ef19879 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -9,7 +9,7 @@ from tests.utils import create_fieldset_unit_mesh, create_simple_pset -def validate_simple_repr(class_: type, kwargs: dict[str, Any]): +def assert_simple_repr(class_: type, kwargs: dict[str, Any]): """Test that the repr of an object contains all the arguments. This only works for objects where the repr matches the calling signature.""" obj = class_(**kwargs) obj_repr = repr(obj) @@ -55,13 +55,13 @@ def test_grid_repr(): kwargs = dict( lon=np.array([1, 2, 3]), lat=np.array([4, 5, 6]), time=None, time_origin=TimeConverter(), mesh="spherical" ) - validate_simple_repr(Grid, kwargs) + assert_simple_repr(Grid, kwargs) def test_variable_repr(): """Test arguments are in the repr of the Variable object.""" kwargs = dict(name="test", dtype=np.float32, initial=0, to_write=False) - validate_simple_repr(Variable, kwargs) + assert_simple_repr(Variable, kwargs) def test_rectilineargrid_repr(): @@ -73,7 +73,7 @@ def test_rectilineargrid_repr(): kwargs = dict( lon=np.array([1, 2, 3]), lat=np.array([4, 5, 6]), time=None, time_origin=TimeConverter(), mesh="spherical" ) - validate_simple_repr(RectilinearGrid, kwargs) + assert_simple_repr(RectilinearGrid, kwargs) def test_particlefile_repr(): @@ -81,7 +81,7 @@ def test_particlefile_repr(): kwargs = dict( name="file.zarr", particleset=pset, outputdt=timedelta(hours=1), chunks=None, create_new_zarrfile=False ) - validate_simple_repr(ParticleFile, kwargs) + assert_simple_repr(ParticleFile, kwargs) def test_field_repr(): @@ -89,6 +89,11 @@ def test_field_repr(): assert valid_indentation(repr(field)) +def test_fieldset_repr(): + fieldset = create_fieldset_unit_mesh() + assert valid_indentation(repr(fieldset)) + + def test_particleset_repr(): pset = create_simple_pset() valid_indentation(repr(pset)) diff --git a/tests/tools/test_helpers.py b/tests/tools/test_helpers.py index c7510c21b..c3499b55a 100644 --- a/tests/tools/test_helpers.py +++ b/tests/tools/test_helpers.py @@ -1,8 +1,19 @@ import pytest +import parcels.tools._helpers as helpers from parcels.tools._helpers import deprecated, deprecated_made_private +def test_format_list_items_multiline(): + expected = """[ + item1, + item2, + item3 +]""" + assert helpers._format_list_items_multiline(["item1", "item2", "item3"], 1) == expected + assert helpers._format_list_items_multiline([], 1) == "[]" + + def test_deprecated(): class SomeClass: @deprecated() From ebfbabf0a0c1ac71000233d6b91b3e2f0a79a877 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:37:43 +0100 Subject: [PATCH 09/16] typing --- parcels/field.py | 3 +++ parcels/tools/_helpers.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/parcels/field.py b/parcels/field.py index ac9772926..7f31941a6 100644 --- a/parcels/field.py +++ b/parcels/field.py @@ -150,6 +150,9 @@ class Field: * `Nested Fields <../examples/tutorial_NestedFields.ipynb>`__ """ + allow_time_extrapolation: bool + time_periodic: TimePeriodic + def __init__( self, name: str | tuple[str, str], diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index 56f2ee490..dd266fd37 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -99,8 +99,8 @@ def _format_list_items_multiline(items: list[str], level: int = 1) -> str: indentation_str = level * 4 * " " indentation_str_end = (level - 1) * 4 * " " - items = ",\n".join([textwrap.indent(i, indentation_str) for i in items]) - return f"[\n{items}\n{indentation_str_end}]" + items_str = ",\n".join([textwrap.indent(i, indentation_str) for i in items]) + return f"[\n{items_str}\n{indentation_str_end}]" def particleset_repr(pset: ParticleSet) -> str: From 39068ee222945eb57d8709d1a2da99c3d85306be Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:06:00 +0100 Subject: [PATCH 10/16] add field name --- parcels/tools/_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index dd266fd37..9a09d361e 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -68,6 +68,7 @@ def patch_docstring(obj: Callable, extra: str) -> None: def field_repr(field: Field) -> str: """Return a pretty repr for Field""" out = f"""<{type(field).__name__}> + name : {field.name!r} grid : {field.grid!r} extrapolate time: {field.allow_time_extrapolation!r} time_periodic : {field.time_periodic!r} From 69c905da404792d8b160d278545315dd992fecce Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:08:03 +0100 Subject: [PATCH 11/16] bugfix pset repr --- parcels/tools/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index 9a09d361e..bd1fccdd4 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -107,7 +107,7 @@ def _format_list_items_multiline(items: list[str], level: int = 1) -> str: def particleset_repr(pset: ParticleSet) -> str: """Return a pretty repr for ParticleSet""" if len(pset) < 10: - particles = [repr(p for p in pset)] + particles = [repr(p) for p in pset] else: particles = [repr(pset[i]) for i in range(7)] + ["..."] From fcb523429bc58c40aa045d2f61f59ef0acca228b Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:14:03 +0100 Subject: [PATCH 12/16] vectorfield repr --- parcels/field.py | 9 ++++++++- tests/test_reprs.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/parcels/field.py b/parcels/field.py index 7f31941a6..d20b5940b 100644 --- a/parcels/field.py +++ b/parcels/field.py @@ -21,7 +21,7 @@ assert_valid_gridindexingtype, assert_valid_interp_method, ) -from parcels.tools._helpers import deprecated_made_private, field_repr +from parcels.tools._helpers import default_repr, deprecated_made_private, field_repr from parcels.tools.converters import ( Geographic, GeographicPolar, @@ -1912,6 +1912,13 @@ def __init__(self, name: str, U: Field, V: Field, W: Field | None = None): assert W.interp_method == "cgrid_velocity", "Interpolation methods of U and W are not the same." assert self._check_grid_dimensions(U.grid, W.grid), "Dimensions of U and W are not the same." + def __repr__(self): + return f"""<{type(self).__name__}> + name: {self.name!r} + U: {default_repr(self.U)} + V: {default_repr(self.V)} + W: {default_repr(self.W)}""" + @staticmethod def _check_grid_dimensions(grid1, grid2): return ( diff --git a/tests/test_reprs.py b/tests/test_reprs.py index 32ef19879..bce4bce4b 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -4,6 +4,7 @@ import numpy as np +import parcels from parcels import Grid, ParticleFile, TimeConverter, Variable from parcels.grid import RectilinearGrid from tests.utils import create_fieldset_unit_mesh, create_simple_pset @@ -89,6 +90,12 @@ def test_field_repr(): assert valid_indentation(repr(field)) +def test_vectorfield_repr(): + field = create_fieldset_unit_mesh().UV + assert isinstance(field, parcels.VectorField) + assert valid_indentation(repr(field)) + + def test_fieldset_repr(): fieldset = create_fieldset_unit_mesh() assert valid_indentation(repr(fieldset)) @@ -100,3 +107,8 @@ def test_particleset_repr(): pset = create_simple_pset(n=15) valid_indentation(repr(pset)) + + +def capture(s): + with open("file.txt", "a") as f: + f.write(s) From 4502fc7006dbafb587b9abe1b8d712d92b2b7ecb Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:18:42 +0100 Subject: [PATCH 13/16] W field repr work around `default_repr` doesn't play well with None types --- parcels/field.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parcels/field.py b/parcels/field.py index d20b5940b..267337c8b 100644 --- a/parcels/field.py +++ b/parcels/field.py @@ -1913,11 +1913,12 @@ def __init__(self, name: str, U: Field, V: Field, W: Field | None = None): assert self._check_grid_dimensions(U.grid, W.grid), "Dimensions of U and W are not the same." def __repr__(self): + w_repr = default_repr(self.W) if self.W is not None else repr(self.W) return f"""<{type(self).__name__}> name: {self.name!r} U: {default_repr(self.U)} V: {default_repr(self.V)} - W: {default_repr(self.W)}""" + W: {w_repr}""" @staticmethod def _check_grid_dimensions(grid1, grid2): From b2e592c3075de2cdba65b0b43a52a43211742235 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:28:19 +0100 Subject: [PATCH 14/16] add pset.pclass attr --- parcels/particleset.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/parcels/particleset.py b/parcels/particleset.py index d83de4686..207b851be 100644 --- a/parcels/particleset.py +++ b/parcels/particleset.py @@ -112,6 +112,7 @@ def __init__( self.fieldset = fieldset self.fieldset._check_complete() self.time_origin = fieldset.time_origin + self._pclass = pclass # ==== first: create a new subclass of the pclass that includes the required variables ==== # # ==== see dynamic-instantiation trick here: https://www.python-course.eu/python3_classes_and_type.php ==== # @@ -385,6 +386,10 @@ def size(self): # ==== to change at some point - len and size are different things ==== # return len(self.particledata) + @property + def pclass(self): + return self._pclass + def __repr__(self): return particleset_repr(self) From 02d36fc359edcc92d560ebd215aa4c64b1c87f17 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:30:31 +0100 Subject: [PATCH 15/16] patch indentation --- parcels/tools/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parcels/tools/_helpers.py b/parcels/tools/_helpers.py index bd1fccdd4..89a299cde 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -116,7 +116,7 @@ def particleset_repr(pset: ParticleSet) -> str: pclass : {pset.pclass} repeatdt : {pset.repeatdt} # particles: {len(pset)} - particles : {_format_list_items_multiline(particles)} + particles : {_format_list_items_multiline(particles, level=2)} """ return textwrap.dedent(out).strip() From 709094c8fce16f365eee5d7b7298e02081607c0c Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 28 Oct 2024 09:41:38 +0100 Subject: [PATCH 16/16] Update Parcels tutorial notebook with new reprs --- docs/examples/parcels_tutorial.ipynb | 216 ++++++++++++++++++++------- 1 file changed, 165 insertions(+), 51 deletions(-) diff --git a/docs/examples/parcels_tutorial.ipynb b/docs/examples/parcels_tutorial.ipynb index 2f11c60f4..102a87419 100644 --- a/docs/examples/parcels_tutorial.ipynb +++ b/docs/examples/parcels_tutorial.ipynb @@ -72,11 +72,41 @@ "cell_type": "code", "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "
\n", + " fields:\n", + " \n", + " name : 'U'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 0.00, 86400.00, 172800.00, ..., 432000.00, 518400.00, 604800.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name : 'V'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 0.00, 86400.00, 172800.00, ..., 432000.00, 518400.00, 604800.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name: 'UV'\n", + " U: \n", + " V: \n", + " W: None\n" + ] + } + ], "source": [ "example_dataset_folder = parcels.download_example_dataset(\"MovingEddies_data\")\n", "\n", - "fieldset = parcels.FieldSet.from_parcels(f\"{example_dataset_folder}/moving_eddies\")" + "fieldset = parcels.FieldSet.from_parcels(f\"{example_dataset_folder}/moving_eddies\")\n", + "\n", + "print(fieldset)" ] }, { @@ -153,11 +183,34 @@ "output_type": "stream", "text": [ "\n", - " fieldset : \n", - " pclass : False\n", + " fieldset :
\n", + " fields:\n", + " \n", + " name : 'U'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 0.00, 86400.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name : 'V'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 0.00, 86400.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name: 'UV'\n", + " U: \n", + " V: \n", + " W: None\n", + " pclass : \n", " repeatdt : None\n", " # particles: 2\n", - " particles : [P[0](lon=330000.000000, lat=100000.000000, depth=0.000000, time=not_yet_set), P[1](lon=330000.000000, lat=280000.000000, depth=0.000000, time=not_yet_set)]\n" + " particles : [\n", + " P[0](lon=330000.000000, lat=100000.000000, depth=0.000000, time=not_yet_set),\n", + " P[1](lon=330000.000000, lat=280000.000000, depth=0.000000, time=not_yet_set)\n", + " ]\n" ] } ], @@ -212,7 +265,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The final step is to run (or 'execute') the `ParticelSet`. We run the particles using the `AdvectionRK4` kernel, which is a 4th order Runge-Kutte implementation that comes with Parcels. We run the particles for 6 days (using the `timedelta` function from `datetime`), at an RK4 timestep of 5 minutes. We store the trajectory information at an interval of 1 hour in a file called `EddyParticles.zarr`. Because `time` was `not_yet_set`, the particles will be advected from the first date available in the `fieldset`, which is the default behaviour.\n" + "We can now create `ParticleFile` objects to store the output of the particles. We will store the output at an interval of 1 hour in a `zarr` file called `EddyParticles.zar`. See the [Working with Parcels output tutorial](https://docs.oceanparcels.org/en/latest/examples/tutorial_output.html) for more information on the `zarr` format." ] }, { @@ -224,17 +277,32 @@ "name": "stdout", "output_type": "stream", "text": [ - "INFO: Output files are stored in EddyParticles.zarr.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/erik/Codes/ParcelsCode/parcels/particleset.py:1082: FileWarning: Some of the particles have a start time that is not a multiple of outputdt. This could cause the first output to be at a different time than expected.\n", - " _warn_outputdt_release_desync(outputdt, self.particledata.data[\"time_nextloop\"])\n" + "ParticleFile(name='EddyParticles.zarr', particleset=, outputdt=3600.0, chunks=None, create_new_zarrfile=True)\n" ] - }, + } + ], + "source": [ + "output_file = pset.ParticleFile(\n", + " name=\"EddyParticles.zarr\", # the file name\n", + " outputdt=timedelta(hours=1), # the time step of the outputs\n", + ")\n", + "\n", + "print(output_file)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final step is to run (or 'execute') the `ParticelSet`. We run the particles using the `AdvectionRK4` kernel, which is a 4th order Runge-Kutte implementation that comes with Parcels. We run the particles for 6 days (using the `timedelta` function from `datetime`), at an RK4 timestep of 5 minutes. Because `time` was `not_yet_set`, the particles will be advected from the first date available in the `fieldset`, which is the default behaviour.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ { "name": "stdout", "output_type": "stream", @@ -244,10 +312,6 @@ } ], "source": [ - "output_file = pset.ParticleFile(\n", - " name=\"EddyParticles.zarr\", # the file name\n", - " outputdt=timedelta(hours=1), # the time step of the outputs\n", - ")\n", "pset.execute(\n", " parcels.AdvectionRK4, # the kernel (which defines how particles move)\n", " runtime=timedelta(days=6), # the total length of the run\n", @@ -266,15 +330,42 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "P[0](lon=226905.562500, lat=82515.218750, depth=0.000000, time=518100.000000)\n", - "P[1](lon=260835.125000, lat=320403.343750, depth=0.000000, time=518100.000000)\n" + "\n", + " fieldset :
\n", + " fields:\n", + " \n", + " name : 'U'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 432000.00, 518400.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name : 'V'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 432000.00, 518400.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name: 'UV'\n", + " U: \n", + " V: \n", + " W: None\n", + " pclass : \n", + " repeatdt : None\n", + " # particles: 2\n", + " particles : [\n", + " P[0](lon=226905.562500, lat=82515.218750, depth=0.000000, time=518100.000000),\n", + " P[1](lon=260835.125000, lat=320403.343750, depth=0.000000, time=518100.000000)\n", + " ]\n" ] }, { @@ -318,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -351,7 +442,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -389,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -13177,7 +13268,7 @@ "" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -13204,7 +13295,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -13239,7 +13330,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -13247,11 +13338,34 @@ "output_type": "stream", "text": [ "\n", - " fieldset : \n", - " pclass : False\n", + " fieldset :
\n", + " fields:\n", + " \n", + " name : 'U'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 0.00, 86400.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name : 'V'\n", + " grid : RectilinearZGrid(lon=array([ 0.00, 2010.05, 4020.10, ..., 395979.91, 397989.94, 400000.00], dtype=float32), lat=array([ 0.00, 2005.73, 4011.46, ..., 695988.56, 697994.25, 700000.00], dtype=float32), time=array([ 0.00, 86400.00]), time_origin=0.0, mesh='flat')\n", + " extrapolate time: False\n", + " time_periodic : False\n", + " gridindexingtype: 'nemo'\n", + " to_write : False\n", + " \n", + " name: 'UV'\n", + " U: \n", + " V: \n", + " W: None\n", + " pclass : \n", " repeatdt : None\n", " # particles: 2\n", - " particles : [P[0](lon=329983.281250, lat=100495.609375, depth=0.000000, time=300.000000), P[1](lon=330289.968750, lat=280418.906250, depth=0.000000, time=300.000000)]\n" + " particles : [\n", + " P[0](lon=329983.281250, lat=100495.609375, depth=0.000000, time=300.000000),\n", + " P[1](lon=330289.968750, lat=280418.906250, depth=0.000000, time=300.000000)\n", + " ]\n" ] }, { @@ -13303,7 +13417,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -13330,7 +13444,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -13368,7 +13482,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -13427,7 +13541,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -13449,7 +13563,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -13470,7 +13584,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -13487,7 +13601,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -13510,7 +13624,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -13544,7 +13658,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -13590,7 +13704,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -13612,7 +13726,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -13642,7 +13756,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -13685,7 +13799,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -13737,7 +13851,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -13797,7 +13911,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ @@ -13824,7 +13938,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -13863,7 +13977,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -13893,7 +14007,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -13926,7 +14040,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "metadata": {}, "outputs": [ {