Skip to content

Conversation

@erikvansebille
Copy link
Member

@erikvansebille erikvansebille commented Jan 7, 2026

This PR implements #2392 by cleaning up the __repr__ for the most important Parcels classes. It now includes

  • FieldSet
  • Field
  • VectorField
  • XGrid
  • TimeInterval
  • ParticleSet
  • ParticleSetView
  • ParticleClass
  • Variable
  • ParticleFile

To be done

  • UxGrid (@fluidnumerics-joe can you take charge of this in this or a next PR?)
  • unit test these implementations. Not sure how to best go about that though. @VeckoTheGecko, any ideas?

See below the result of these __reprs__ for the example of the tutorial_output.ipynb (after the pset.execute())

> print(fieldset)
<FieldSet>
    fields:
        <Field 'U'>
            Parcels attributes:
                name            : 'U'
                interp_method   : <function XLinear at 0x3092394e0>
                time_interval   : TimeInterval(left=np.datetime64('2024-01-01T00:00:00.000000000'), right=np.datetime64('2024-02-01T00:00:00.000000000'))
                units           : <parcels._core.converters.GeographicPolar object at 0x3097f7f80>
                igrid           : -1
            DataArray:
                <xarray.DataArray 'U' (time: 32, depth: 41, lat: 37, lon: 25)> Size: 5MB
                0.07719 0.07689 0.0733 0.06869 0.06567 0.06535 ... nan nan nan nan nan nan
                Coordinates:
                  * depth    (depth) float32 164B 0.494 1.541 2.646 ... 1.942e+03 2.225e+03
                  * lat      (lat) float32 148B -33.0 -32.92 -32.83 ... -30.17 -30.08 -30.0
                  * lon      (lon) float32 100B 31.0 31.08 31.17 31.25 ... 32.83 32.92 33.0
                  * time     (time) datetime64[ns] 256B 2024-01-01 2024-01-02 ... 2024-02-01
                Attributes:
                    long_name:      Eastward velocity
                    valid_max:      5.0
                    units:          m s-1
                    standard_name:  eastward_sea_water_velocity
                    unit_long:      Meters per second
                    valid_min:      -5.0
            <XGrid>
                Parcels attributes:
                    mesh                  : spherical
                    spatialhash           : None
                xgcm Grid:
                    <xgcm.Grid>
                    X Axis (not periodic, boundary='fill'):
                      * right    lon
                    Y Axis (not periodic, boundary='fill'):
                      * right    lat
                    Z Axis (not periodic, boundary='fill'):
                      * right    depth
                    T Axis (not periodic, boundary='fill'):
                      * center   time
        <Field 'V'>
            Parcels attributes:
                name            : 'V'
                interp_method   : <function XLinear at 0x3092394e0>
                time_interval   : TimeInterval(left=np.datetime64('2024-01-01T00:00:00.000000000'), right=np.datetime64('2024-02-01T00:00:00.000000000'))
                units           : <parcels._core.converters.Geographic object at 0x3092b9670>
                igrid           : -1
            DataArray:
                <xarray.DataArray 'V' (time: 32, depth: 41, lat: 37, lon: 25)> Size: 5MB
                -0.2087 -0.2253 -0.229 -0.2215 -0.2056 -0.1791 ... nan nan nan nan nan nan
                Coordinates:
                  * depth    (depth) float32 164B 0.494 1.541 2.646 ... 1.942e+03 2.225e+03
                  * lat      (lat) float32 148B -33.0 -32.92 -32.83 ... -30.17 -30.08 -30.0
                  * lon      (lon) float32 100B 31.0 31.08 31.17 31.25 ... 32.83 32.92 33.0
                  * time     (time) datetime64[ns] 256B 2024-01-01 2024-01-02 ... 2024-02-01
                Attributes:
                    long_name:      Northward velocity
                    valid_max:      5.0
                    units:          m s-1
                    standard_name:  northward_sea_water_velocity
                    unit_long:      Meters per second
                    valid_min:      -5.0
            <XGrid>
                Parcels attributes:
                    mesh                  : spherical
                    spatialhash           : None
                xgcm Grid:
                    <xgcm.Grid>
                    X Axis (not periodic, boundary='fill'):
                      * right    lon
                    Y Axis (not periodic, boundary='fill'):
                      * right    lat
                    Z Axis (not periodic, boundary='fill'):
                      * right    depth
                    T Axis (not periodic, boundary='fill'):
                      * center   time
        <Field 'thetao'>
            Parcels attributes:
                name            : 'thetao'
                interp_method   : <function XLinear at 0x3092394e0>
                time_interval   : TimeInterval(left=np.datetime64('2024-01-01T00:00:00.000000000'), right=np.datetime64('2024-02-01T00:00:00.000000000'))
                units           : <parcels._core.converters.Unity object at 0x17e0a8200>
                igrid           : -1
            DataArray:
                <xarray.DataArray 'thetao' (time: 32, depth: 41, lat: 37, lon: 25)> Size: 5MB
                22.29 22.4 22.5 22.61 22.69 22.72 22.69 22.61 ... nan nan nan nan nan nan nan
                Coordinates:
                  * depth    (depth) float32 164B 0.494 1.541 2.646 ... 1.942e+03 2.225e+03
                  * lat      (lat) float32 148B -33.0 -32.92 -32.83 ... -30.17 -30.08 -30.0
                  * lon      (lon) float32 100B 31.0 31.08 31.17 31.25 ... 32.83 32.92 33.0
                  * time     (time) datetime64[ns] 256B 2024-01-01 2024-01-02 ... 2024-02-01
                Attributes:
                    long_name:      Temperature
                    valid_max:      40.0
                    units:          degrees_C
                    standard_name:  sea_water_potential_temperature
                    unit_long:      Degrees Celsius
                    valid_min:      -10.0
            <XGrid>
                Parcels attributes:
                    mesh                  : spherical
                    spatialhash           : None
                xgcm Grid:
                    <xgcm.Grid>
                    X Axis (not periodic, boundary='fill'):
                      * right    lon
                    Y Axis (not periodic, boundary='fill'):
                      * right    lat
                    Z Axis (not periodic, boundary='fill'):
                      * right    depth
                    T Axis (not periodic, boundary='fill'):
                      * center   time
        <Field 'so'>
            Parcels attributes:
                name            : 'so'
                interp_method   : <function XLinear at 0x3092394e0>
                time_interval   : TimeInterval(left=np.datetime64('2024-01-01T00:00:00.000000000'), right=np.datetime64('2024-02-01T00:00:00.000000000'))
                units           : <parcels._core.converters.Unity object at 0x104cb8dd0>
                igrid           : -1
            DataArray:
                <xarray.DataArray 'so' (time: 32, depth: 41, lat: 37, lon: 25)> Size: 5MB
                35.62 35.62 35.62 35.62 35.6 35.6 35.6 35.6 ... nan nan nan nan nan nan nan nan
                Coordinates:
                  * depth    (depth) float32 164B 0.494 1.541 2.646 ... 1.942e+03 2.225e+03
                  * lat      (lat) float32 148B -33.0 -32.92 -32.83 ... -30.17 -30.08 -30.0
                  * lon      (lon) float32 100B 31.0 31.08 31.17 31.25 ... 32.83 32.92 33.0
                  * time     (time) datetime64[ns] 256B 2024-01-01 2024-01-02 ... 2024-02-01
                Attributes:
                    standard_name:  sea_water_salinity
                    valid_max:      50.0
                    valid_min:      0.0
                    long_name:      Salinity
                    unit_long:      Practical Salinity Unit
                    units:          1e-3
            <XGrid>
                Parcels attributes:
                    mesh                  : spherical
                    spatialhash           : None
                xgcm Grid:
                    <xgcm.Grid>
                    X Axis (not periodic, boundary='fill'):
                      * right    lon
                    Y Axis (not periodic, boundary='fill'):
                      * right    lat
                    Z Axis (not periodic, boundary='fill'):
                      * right    depth
                    T Axis (not periodic, boundary='fill'):
                      * center   time
    vectorfields:
        <VectorField 'UV'>
            Parcels attributes:
                name                  : 'UV'
                vector_interp_method  : None
                vector_type           : '2D'
print(pset)
<ParticleSet>
    Number of particles: 10
    Particles:
        P[0]: time=172800.000000, z=0.494025, lat=-32.202419, lon=31.948809
        P[1]: time=172800.000000, z=0.494025, lat=-31.880075, lon=31.900625
        P[2]: time=172800.000000, z=0.494025, lat=-31.525352, lon=32.030704
        P[3]: time=172800.000000, z=0.494025, lat=-31.362623, lon=31.953291
        P[4]: time=172800.000000, z=0.494025, lat=-31.425129, lon=31.551485
        P[5]: time=172800.000000, z=0.494025, lat=-31.567675, lon=31.239336
        P[6]: time=172800.000000, z=0.494025, lat=-31.506435, lon=31.225775
        ...
        P[9]: time=172800.000000, z=0.494025, lat=-30.785074, lon=31.691055
    Pclass:
        Variable(name='lon', dtype=dtype('float32'), initial=0, to_write=True, attrs={'standard_name': 'longitude', 'units': 'degrees_east', 'axis': 'X'})
        Variable(name='lat', dtype=dtype('float32'), initial=0, to_write=True, attrs={'standard_name': 'latitude', 'units': 'degrees_north', 'axis': 'Y'})
        Variable(name='z', dtype=dtype('float32'), initial=0, to_write=True, attrs={'standard_name': 'vertical coordinate', 'units': 'm', 'positive': 'down'})
        Variable(name='dlon', dtype=dtype('float32'), initial=0, to_write=False, attrs={})
        Variable(name='dlat', dtype=dtype('float32'), initial=0, to_write=False, attrs={})
        Variable(name='dz', dtype=dtype('float32'), initial=0, to_write=False, attrs={})
        Variable(name='time', dtype=dtype('float64'), initial=0, to_write=True, attrs={'standard_name': 'time', 'units': 'seconds', 'axis': 'T'})
        Variable(name='trajectory', dtype=dtype('int64'), initial=0, to_write='once', attrs={'long_name': 'Unique identifier for each particle', 'cf_role': 'trajectory_id'})
        Variable(name='obs_written', dtype=dtype('int32'), initial=0, to_write=False, attrs={})
        Variable(name='dt', dtype=dtype('float64'), initial=1.0, to_write=False, attrs={})
        Variable(name='state', dtype=dtype('int32'), initial=10, to_write=False, attrs={})
print(output_file)
<ParticleFile>
    store               : /Users/erik/Codes/ParcelsCode/docs/getting_started/output.zarr
    outputdt            : 7200.0
    chunks              : (10, 1)
    create_new_zarrfile : False
    metadata            :
        date_created: 2026-01-07T15:29:19.269640
        feature_type: trajectory
        Conventions: CF-1.6/CF-1.7
        ncei_template_version: NCEI_NetCDF_Trajectory_Template_v2.0
        parcels_version: 3.1.3.dev293
        parcels_grid_mesh: spherical
        parcels_kernels: AdvectionRK4

@VeckoTheGecko
Copy link
Contributor

I think that some of these reprs for the complex objects are too verbose.

e.g., for a fieldset, I can imagine a user when printing an object only really cares about the names of the available fields, vectorfields, constants, and values for constants - so that they can write their kernels. I don't think they care about the underlying data for the fields (and they can poke at that if they want anyway). Especially in v4 where they should be checking their data before they pass to Parcels.

I think having a repr that is too verbose is a downside since it floods the output meaning they have to pick through for items of interest. I think there's also an argument to be made that maybe we should be teaching users via the docs that they should just use dir(...) if they want to inspect objects deeply.

Deciding what's important to users (new and migrating from v3), and hence what we want to communicate to them via reprs or via other ways is difficult to do without having alpha release testing - I don't think we can make an informed decision now. I'm erring on the side of postponing this

From a dev POV, do you feel like any of the reprs in v4-dev are lacking in ways that impact your workflow? If so, maybe we can provide some (minimal) reprs for now and revisit later.

Comment on lines +113 to +120
def variable_repr(var: Any) -> str:
"""Return a pretty repr for Variable"""
return f"Variable(name={var._name!r}, dtype={var.dtype!r}, initial={var.initial!r}, to_write={var.to_write!r}, attrs={var.attrs!r})"


def timeinterval_repr(ti: Any) -> str:
"""Return a pretty repr for TimeInterval"""
return f"TimeInterval(left={ti.left!r}, right={ti.right!r})"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we move these back? For short bodies, or oneliners like these which don't use any formatting helper functions I think its better to keep them with the rest of the class body rather than in a separate file

@VeckoTheGecko
Copy link
Contributor

VeckoTheGecko commented Jan 9, 2026

Saying all that - once #2452 (comment) is resolved I'm also open to merging. These are very orthogonal changes and we can iterate on gradually :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

3 participants