diff --git a/docs/examples/parcels_tutorial.ipynb b/docs/examples/parcels_tutorial.ipynb index 5936644aa..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)" ] }, { @@ -152,8 +182,35 @@ "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", + " 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 : [\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" ] } ], @@ -208,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." ] }, { @@ -220,8 +277,7 @@ "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" + "ParticleFile(name='EddyParticles.zarr', particleset=, outputdt=3600.0, chunks=None, create_new_zarrfile=True)\n" ] } ], @@ -230,6 +286,32 @@ " 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", + "text": [ + "100%|██████████| 518400.0/518400.0 [00:05<00:00, 95421.55it/s] \n" + ] + } + ], + "source": [ "pset.execute(\n", " parcels.AdvectionRK4, # the kernel (which defines how particles move)\n", " runtime=timedelta(days=6), # the total length of the run\n", @@ -248,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" ] }, { @@ -300,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -333,7 +442,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -371,7 +480,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -13159,7 +13268,7 @@ "" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -13186,7 +13295,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -13221,15 +13330,42 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { "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", + " 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 : [\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" ] }, { @@ -13281,7 +13417,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -13308,7 +13444,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -13346,7 +13482,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -13405,7 +13541,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -13427,7 +13563,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -13448,7 +13584,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -13465,7 +13601,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -13488,7 +13624,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -13522,7 +13658,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -13568,7 +13704,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -13590,7 +13726,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -13620,7 +13756,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -13663,7 +13799,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -13715,7 +13851,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -13775,7 +13911,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ @@ -13802,7 +13938,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -13841,7 +13977,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -13871,7 +14007,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -13904,7 +14040,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "metadata": {}, "outputs": [ { diff --git a/parcels/field.py b/parcels/field.py index c7f5d2eac..a8a79dfc6 100644 --- a/parcels/field.py +++ b/parcels/field.py @@ -20,7 +20,7 @@ assert_valid_gridindexingtype, assert_valid_interp_method, ) -from parcels.tools._helpers import deprecated_made_private, timedelta_to_float +from parcels.tools._helpers import default_repr, deprecated_made_private, field_repr, timedelta_to_float from parcels.tools.converters import ( Geographic, GeographicPolar, @@ -149,6 +149,8 @@ class Field: * `Nested Fields <../examples/tutorial_NestedFields.ipynb>`__ """ + allow_time_extrapolation: bool + time_periodic: TimePeriodic _cast_data_dtype: type[np.float32] | type[np.float64] def __init__( @@ -321,6 +323,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 field_repr(self) + @property @deprecated_made_private # TODO: Remove 6 months after v3.1.0 def dataFiles(self): @@ -1915,6 +1920,14 @@ 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): + 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: {w_repr}""" + @staticmethod def _check_grid_dimensions(grid1, grid2): return ( diff --git a/parcels/fieldset.py b/parcels/fieldset.py index aad0635fa..9a7fd39da 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/particle.py b/parcels/particle.py index c7f1681e0..87dce269f 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/parcels/particlefile.py b/parcels/particlefile.py index c8482b9e6..4ab406b4a 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, timedelta_to_float +from parcels.tools._helpers import default_repr, deprecated, deprecated_made_private, timedelta_to_float 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/particleset.py b/parcels/particleset.py index 1fde32ca6..ffe7eb652 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, timedelta_to_float +from parcels.tools._helpers import deprecated, deprecated_made_private, particleset_repr, timedelta_to_float 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 @@ -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 ==== # @@ -386,8 +387,12 @@ 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 "\n".join([str(p) for p in 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 24c8ba9c4..9078690cc 100644 --- a/parcels/tools/_helpers.py +++ b/parcels/tools/_helpers.py @@ -1,12 +1,19 @@ """Internal helpers for Parcels.""" +from __future__ import annotations + import functools +import textwrap import warnings from collections.abc import Callable from datetime import timedelta +from typing import TYPE_CHECKING, Any import numpy as np +if TYPE_CHECKING: + from parcels import Field, FieldSet, ParticleSet + PACKAGE = "Parcels" @@ -61,6 +68,77 @@ def patch_docstring(obj: Callable, extra: str) -> None: obj.__doc__ = f"{obj.__doc__ or ''}{extra}".strip() +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} + gridindexingtype: {field.gridindexingtype!r} + to_write : {field.to_write!r} +""" + 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 * " " + + 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: + """Return a pretty repr for ParticleSet""" + if len(pset) < 10: + particles = [repr(p) for p in pset] + else: + 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 : {_format_list_items_multiline(particles, level=2)} +""" + 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 textwrap.dedent(out).strip() + + +def default_repr(obj: Any): + return object.__repr__(obj) + + def timedelta_to_float(dt: float | timedelta | np.timedelta64) -> float: """Convert a timedelta to a float in seconds.""" if isinstance(dt, timedelta): diff --git a/tests/test_reprs.py b/tests/test_reprs.py index d072ded9f..bce4bce4b 100644 --- a/tests/test_reprs.py +++ b/tests/test_reprs.py @@ -1,12 +1,16 @@ +import re +from datetime import timedelta from typing import Any import numpy as np -from parcels import Grid, TimeConverter +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 -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) @@ -17,12 +21,48 @@ def validate_simple_repr(class_: type, kwargs: dict[str, Any]): assert class_.__name__ in obj_repr +def valid_indentation(s: str) -> bool: + """Make sure that all lines in string is indented with a multiple of 4 spaces.""" + if s.startswith(" "): + return False + + lines = s.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( 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) + assert_simple_repr(Variable, kwargs) def test_rectilineargrid_repr(): @@ -34,4 +74,41 @@ 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(): + pset = create_simple_pset() + kwargs = dict( + name="file.zarr", particleset=pset, outputdt=timedelta(hours=1), chunks=None, create_new_zarrfile=False + ) + assert_simple_repr(ParticleFile, kwargs) + + +def test_field_repr(): + field = create_fieldset_unit_mesh().U + 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)) + + +def test_particleset_repr(): + pset = create_simple_pset() + valid_indentation(repr(pset)) + + pset = create_simple_pset(n=15) + valid_indentation(repr(pset)) + + +def capture(s): + with open("file.txt", "a") as f: + f.write(s) diff --git a/tests/tools/test_helpers.py b/tests/tools/test_helpers.py index 3e6f5a992..1403b679f 100644 --- a/tests/tools/test_helpers.py +++ b/tests/tools/test_helpers.py @@ -3,9 +3,20 @@ import numpy as np import pytest +import parcels.tools._helpers as helpers from parcels.tools._helpers import deprecated, deprecated_made_private, timedelta_to_float +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() diff --git a/tests/utils.py b/tests/utils.py index a5153f6be..14ef8b7e5 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,18 @@ def create_fieldset_zeros_conversion(mesh="spherical", xdim=200, ydim=100, mesh_ return FieldSet.from_data(data, dimensions, mesh=mesh) +def create_simple_pset(n=1): + zeros = np.zeros(n) + return parcels.ParticleSet( + fieldset=create_fieldset_unit_mesh(), + pclass=parcels.ScipyParticle, + lon=zeros, + lat=zeros, + depth=zeros, + time=zeros, + ) + + 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