Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coord print #4499

Merged
merged 28 commits into from
Jan 25, 2022
Merged

Coord print #4499

merged 28 commits into from
Jan 25, 2022

Conversation

pp-mo
Copy link
Member

@pp-mo pp-mo commented Jan 13, 2022

Closes #4488, #4494
Linked to #4501

This is just an initial draft version, for discussing the new behaviour + printed output format

Please review for behaviours (output results), and not the code (yet!)

Some general notes :

  • I tried to mimic the general form of the Cube.__str__ output
  • the __repr__ produces a minimal one-line form, and the __str__ a detailed multiline printout, like the Cube ones

@pp-mo
Copy link
Member Author

pp-mo commented Jan 13, 2022

Sample results :

$ python simple_coord_print_tests.py 

Small section of latitude coord[:2]
------------REPR:-------
<DimCoord: latitude / (degrees)  [90. , 87.5]>
------------STR:--------
DimCoord :  latitude / (degrees)
    [89.999985, 87.499985]
    shape: (2,)
    dtype: float32
    standard_name: 'latitude'
------------------------

co[:2] with 4x bounds + printoptions(precision=3) 
------------REPR:-------
<DimCoord: latitude / (degrees)  [90. , 87.5]>
------------STR:--------
DimCoord :  latitude / (degrees)
    [90. , 87.5]
    bounds=[
        [90.  , 88.75, 90.  , 88.75],
        [88.75, 86.25, 88.75, 86.25]]
    shape: (2,) ; bounds=(2, 4)
    dtype: float32
    standard_name: 'latitude'
------------------------


Longer section co[:5]
------------REPR:-------
<DimCoord: latitude / (degrees)  [90. , 87.5, 85. , 82.5, 80. ]>
------------STR:--------
DimCoord :  latitude / (degrees)
    [89.999985, 87.499985, 84.999985, 82.499985, 79.99999 ]
    shape: (5,)
    dtype: float32
    standard_name: 'latitude'
------------------------


Longer section co[:5] with bounds
------------REPR:-------
<DimCoord: latitude / (degrees)  [90. , 87.5, 85. , 82.5, 80. ]>
------------STR:--------
DimCoord :  latitude / (degrees)
    [89.999985, 87.499985, 84.999985, 82.499985, 79.99999 ]
    bounds=[
        [90.      , 88.749985],
        [88.749985, 86.249985],
        [86.249985, 83.749985],
        [83.749985, 81.249985],
        [81.249985, 78.74999 ]]
    shape: (5,) ; bounds=(5, 2)
    dtype: float32
    standard_name: 'latitude'
------------------------


Still longer co[-15:] with bounds
------------REPR:-------
<DimCoord: latitude / (degrees)  [-55. , -57.5, ..., -87.5, -90. ], shape=(15,)>
------------STR:--------
DimCoord :  latitude / (degrees)
    [-54.99994, -57.49994, -59.99994, -62.49994, -64.99994,
     -67.49994, -69.99994, -72.49994, -74.99994, -77.49994,
     -79.99994, -82.49994, -84.99994, -87.49992, -89.99992]
    bounds=[
        [-53.749947, -56.24994 ],
        [-56.24994 , -58.74994 ],
        ...,
        [-86.24993 , -88.74992 ],
        [-88.74992 , -90.      ]]
    shape: (15,) ; bounds=(15, 2)
    dtype: float32
    standard_name: 'latitude'
------------------------


Full co
------------REPR:-------
<DimCoord: latitude / (degrees)  [ 90. , 87.5, ..., -87.5, -90. ], shape=(73,)>
------------STR:--------
DimCoord :  latitude / (degrees)
    [ 89.999985,  87.499985, ..., -87.49992 , -89.99992 ]
    shape: (73,)
    dtype: float32
    standard_name: 'latitude'
------------------------


Full co with bounds
------------REPR:-------
<DimCoord: latitude / (degrees)  [ 90. , 87.5, ..., -87.5, -90. ], shape=(73,)>
------------STR:--------
DimCoord :  latitude / (degrees)
    [ 89.999985,  87.499985, ..., -87.49992 , -89.99992 ]
    bounds=[
        [ 90.      ,  88.749985],
        [ 88.749985,  86.249985],
        ...,
        [-86.24993 , -88.74992 ],
        [-88.74992 , -90.      ]]
    shape: (73,) ; bounds=(73, 2)
    dtype: float32
    standard_name: 'latitude'
------------------------


Small co with lazy points
dask.array<array, shape=(4,), dtype=float32, chunksize=(4,), chunktype=numpy.ndarray>
------------REPR:-------
<AuxCoord: latitude / (degrees)  <lazy>, shape=(4,)>
------------STR:--------
AuxCoord :  latitude / (degrees)
    <lazy>
    shape: (4,)
    dtype: float32
    standard_name: 'latitude'
------------------------


Small co with real points but lazy bounds
------------REPR:-------
<AuxCoord: latitude / (degrees)  <lazy>, shape=(4,)>
------------STR:--------
AuxCoord :  latitude / (degrees)
    <lazy>
    shape: (4,) ; bounds=(4, 2)
    dtype: float32
    standard_name: 'latitude'
------------------------


Float with masked points
------------REPR:-------
<AuxCoord: latitude / (degrees)  [89.999985, -- , ..., 69.99999 , 67.5 ], shape=(10,)>
------------STR:--------
AuxCoord :  latitude / (degrees)
    [89.999985, --       , 84.999985, --       , 79.99999 ,
     77.49999 , 74.99999 , 72.49999 , 69.99999 , 67.5     ]
    shape: (10,)
    dtype: float32
    standard_name: 'latitude'
------------------------


Integer
------------REPR:-------
<AuxCoord: integer_points / (unknown)  [0, 1, 2, 3, 4]>
------------STR:--------
AuxCoord :  integer_points / (unknown)
    [0, 1, 2, 3, 4]
    shape: (5,)
    dtype: int64
    long_name: 'integer_points'
------------------------


Integer with masked points
------------REPR:-------
<AuxCoord: integer_points / (unknown)  [0 , --, 2 , --, 4 ]>
------------STR:--------
AuxCoord :  integer_points / (unknown)
    [0 , --, 2 , --, 4 ]
    shape: (5,)
    dtype: int64
    long_name: 'integer_points'
------------------------


Longer integers with masked points
------------REPR:-------
<AuxCoord: integer_points / (unknown)  [995 , -- , ..., 1018, 1019], shape=(25,)>
------------STR:--------
AuxCoord :  integer_points / (unknown)
    [995 , --  , ..., 1018, 1019]
    shape: (25,)
    dtype: int64
    long_name: 'integer_points'
    var_name: 'qq'
------------------------


dates with bounds
------------REPR:-------
<AuxCoord: x / (days since 2015-05-17)  shape=(10,)>
------------STR:--------
AuxCoord :  x / (days since 2015-05-17)
    [2015-05-17 00:00:00, 2015-05-28 02:40:00,
     2015-06-08 05:20:00, 2015-06-19 08:00:00,
     2015-06-30 10:40:00, 2015-07-11 13:20:00,
     2015-07-22 16:00:00, 2015-08-02 18:40:00,
     2015-08-13 21:20:00, 2015-08-25 00:00:00]
    bounds=[
        [2015-05-11 10:40:00, 2015-05-22 13:20:00],
        [2015-05-22 13:20:00, 2015-06-02 16:00:00],
        ...,
        [2015-08-08 08:00:00, 2015-08-19 10:40:00],
        [2015-08-19 10:40:00, 2015-08-30 13:20:00]]
    shape: (10,) ; bounds=(10, 2)
    dtype: float64
    var_name: 'x'
    attributes: {'a': 14, 'b': None}
------------------------


dates with masked points
------------REPR:-------
<AuxCoord: x / (days since 2015-05-17)  shape=(10,)>
------------STR:--------
AuxCoord :  x / (days since 2015-05-17)
    [2015-05-17 00:00:00, 2015-05-28 02:40:00,
     --                 , 2015-06-19 08:00:00,
     2015-06-30 10:40:00, --                 ,
     2015-07-22 16:00:00, 2015-08-02 18:40:00,
     2015-08-13 21:20:00, 2015-08-25 00:00:00]
    bounds=[
        [2015-05-11 10:40:00, 2015-05-22 13:20:00],
        [2015-05-22 13:20:00, 2015-06-02 16:00:00],
        ...,
        [2015-08-08 08:00:00, 2015-08-19 10:40:00],
        [2015-08-19 10:40:00, 2015-08-30 13:20:00]]
    shape: (10,) ; bounds=(10, 2)
    dtype: float64
    var_name: 'x'
    attributes: {'a': 14, 'b': None}
------------------------


multidimensional
------------REPR:-------
<AuxCoord: unknown / (unknown)  shape=(7, 5, 4)>
------------STR:--------
AuxCoord :  unknown / (unknown)
    [[[2.06187124e+00, 2.62729909e+02, 5.04530999e-01,
       7.72296297e-02],
      [1.98149735e+02, 2.72691428e+00, 5.59201397e-01,
       9.15158572e-02],
      ...,
      [9.35959507e-02, 9.87648075e+01, 1.41555682e+02,
       1.19528981e+01],
      [3.96118914e+00, 5.55304391e-02, 3.79748850e+02,
       1.38359365e+01]],
    
     [[2.88366966e+00, 3.93076950e+01, 5.14994973e+00,
       2.43560359e+00],
      [2.29709442e+00, 5.93655905e-01, 1.12286999e-01,
       1.59593353e-01],
      ...,
      [3.64811663e+02, 2.43749085e+00, 5.07759208e-01,
       5.24134052e+01],
      [1.93544347e+00, 4.15162702e+00, 3.19379729e+02,
       2.75618918e+00]],
    
     ...,
    
     [[1.31830607e+00, 2.51751930e+00, 3.66400899e-01,
       1.76748038e+00],
      [3.85791606e+00, 1.43061739e-01, 2.38357293e-01,
       5.11634593e+00],
      ...,
      [1.43894872e+02, 2.35019027e+01, 5.90061070e+01,
       1.88762605e+02],
      [3.44989074e+01, 1.06243423e+00, 1.14218976e+00,
       7.91787038e+01]],
    
     [[9.12835821e+00, 2.49120964e+02, 1.15961382e+00,
       1.05658599e+02],
      [1.99595152e-01, 3.33424239e+01, 3.64210805e+02,
       3.50297194e+01],
      ...,
      [1.26869326e+02, 3.58661096e+00, 1.92530940e+00,
       5.61565460e+01],
      [1.00202310e+00, 6.23401913e+00, 1.36128130e+01,
       6.95941520e+00]]]
    shape: (7, 5, 4)
    dtype: float64
------------------------


small multidim
------------REPR:-------
<AuxCoord: unknown / (unknown)  [[ 2.062, 262.73 ], [198.15 , 2.727]]>
------------STR:--------
AuxCoord :  unknown / (unknown)
    [[  2.06187124, 262.72990877],
     [198.14973544,   2.72691428]]
    shape: (2, 2)
    dtype: float64
------------------------


SCALAR:
------------REPR:-------
<AuxCoord: forecast_period / (hours)  [15.]>
------------STR:--------
AuxCoord :  forecast_period / (hours)
    [15.]
    shape: (1,)
    dtype: float64
    standard_name: 'forecast_period'
------------------------


==== REAL DATA nc test cube coords ...
------------REPR:-------
<DimCoord: time / (days since 1850-01-01)  shape=(4,)>
------------STR:--------
DimCoord :  time / (days since 1850-01-01)
    [2046-01-01 12:00:00, 2046-01-02 12:00:00,
     2046-01-03 12:00:00, 2046-01-04 12:00:00]
    bounds=[
        [2046-01-01 00:00:00, 2046-01-02 00:00:00],
        [2046-01-02 00:00:00, 2046-01-03 00:00:00],
        [2046-01-03 00:00:00, 2046-01-04 00:00:00],
        [2046-01-04 00:00:00, 2046-01-05 00:00:00]]
    shape: (4,) ; bounds=(4, 2)
    dtype: float64
    standard_name: 'time'
    long_name: 'time'
    var_name: 'time'
------------------------

------------REPR:-------
<DimCoord: latitude / (degrees)  [-89.142, -88.029, ..., 88.029, 89.142], shape=(160,)>
------------STR:--------
DimCoord :  latitude / (degrees)
    [-89.14151943, -88.02942887, ...,  88.02942887,
      89.14151943]
    bounds=[
        [-90.        , -88.62472856],
        [-88.62472856, -87.49119059],
        ...,
        [ 87.49119059,  88.62472856],
        [ 88.62472856,  90.        ]]
    shape: (160,) ; bounds=(160, 2)
    dtype: float64
    standard_name: 'latitude'
    long_name: 'latitude'
    var_name: 'lat'
------------------------

------------REPR:-------
<DimCoord: longitude / (degrees)  [ 0. , 1.125, ..., 357.75 , 358.875], shape=(320,)>
------------STR:--------
DimCoord :  longitude / (degrees)
    [  0.   ,   1.125, ..., 357.75 , 358.875]
    bounds=[
        [ -0.5625,   0.5625],
        [  0.5625,   1.6875],
        ...,
        [357.1875, 358.3125],
        [358.3125, 359.4375]]
    shape: (320,) ; bounds=(320, 2)
    dtype: float64
    standard_name: 'longitude'
    long_name: 'longitude'
    var_name: 'lon'
------------------------

------------REPR:-------
<DimCoord: height / (m)  [10.]>
------------STR:--------
DimCoord :  height / (m)
    [10.]
    shape: (1,)
    dtype: float64
    standard_name: 'height'
    long_name: 'height'
    var_name: 'height'
    attributes: {'positive': 'up'}
------------------------

@rcomer
Copy link
Member

rcomer commented Jan 13, 2022

I just tried this out with a file I had lying around. I think it's a big improvement as there is less mess all over the screen! I also really like that you include the shapes and dtype.

Sometimes, though, it is useful to print the lot out in order to trawl through and figure out what's going wrong! Of course we can print the points and bounds separately and for time coordinates I was thinking I could do

print(coord.units.num2date(coord.points))

but that gave me this, which I think is slightly hideous:

[cftime.DatetimeGregorian(1993, 10, 25, 0, 0, 0, 0, has_year_zero=False)
 cftime.DatetimeGregorian(1993, 11, 1, 0, 0, 0, 0, has_year_zero=False)
 cftime.DatetimeGregorian(1993, 11, 9, 0, 0, 0, 0, has_year_zero=False)
 cftime.DatetimeGregorian(1994, 10, 25, 0, 0, 0, 0, has_year_zero=False)
 cftime.DatetimeGregorian(1994, 11, 1, 0, 0, 0, 0, has_year_zero=False)
 cftime.DatetimeGregorian(1994, 11, 9, 0, 0, 0, 0, has_year_zero=False)
...etc...

Is there a way the user could easily access the time-formatting used by the coord __str__ and apply it to the arrays?

@rcomer
Copy link
Member

rcomer commented Jan 13, 2022

Never mind. I just realised I can do

print(coord.units.num2date(coord.points).astype(str))

@pp-mo
Copy link
Member Author

pp-mo commented Jan 13, 2022

Is there a way the user could easily access the time-formatting used by the coord str

It's an interesting point, and I think there's still scope for a helper to do this.
However it needs the context of the Coord units, yet for consistency should operate on the specific coord points/bounds.
So it would probably need to be something like Coord.points_as_dates() / Coord.bounds_as_dates().
I'm not sure if I think that is a great idea or not.

Another way would be to provide a utility equivalent to the num2date(...).astype(str) you did above, E.G. points_to_dates(array, units). But maybe the oneliner is OK, and at most needs documenting as handy wrinkle ?

@rcomer
Copy link
Member

rcomer commented Jan 13, 2022

Another way would be to provide a utility equivalent to the num2date(...).astype(str) you did above, E.G. points_to_dates(array, units). But maybe the oneliner is OK, and at most needs documenting as handy wrinkle ?

Would that be a job for cf_units rather than iris? E.g. Unit.num2datestr(array), though it would only save the user from typing astype(str) so I'm not sure whether it's worth it. This also reminds me of #4177 where we decided that such conveniences were overdoing it....

@pp-mo
Copy link
Member Author

pp-mo commented Jan 13, 2022

Mods trying out some newer ideas -- see #4501 (reply in thread)

@rcomer
Copy link
Member

rcomer commented Jan 14, 2022

something like Coord.points_as_dates() / Coord.bounds_as_dates(). I'm not sure if I think that is a great idea or not.

Having slept on it, how about something like Coord.print_values(array)? Then I can pass it Coord.points or Coord.bounds and it should be transparent to me that I am realising any lazy arrays.

@pp-mo
Copy link
Member Author

pp-mo commented Jan 17, 2022

some new tweaks.
Still not ready : needs proper tests, and some consideration (at least initial) for printout of other _DimMeta subclasses
I think that is : CellMeasure / AncillaryVariable / MeshCoord

@pp-mo
Copy link
Member Author

pp-mo commented Jan 17, 2022

@rcomer how about something like Coord.print_values(array).

Still thinking about that, but let's make it a separate issue ?

@pp-mo
Copy link
Member Author

pp-mo commented Jan 17, 2022

In case anyone doesn't get a ping from the Discussion, I just raised there an awkward question of column formatting ...
#4501 (comment)

@pp-mo
Copy link
Member Author

pp-mo commented Jan 18, 2022

( Note: rebased onto upstream/main ; removed some test-debug code )

Still TODO:

  • full testing of more aspects
    • size-related partial-array printout types
    • alternative cases : lazy, masked
    • non-Coord operations : CellMeasure; AncillaryVariable; MeshCoord
  • reorganise attribute printouts, in line with the Cube version (cf comment)
  • fix MeshCoord and Mesh printout (maybe in another PR ?)

@pp-mo
Copy link
Member Author

pp-mo commented Jan 19, 2022

Finally done !
There was a lot of knock-on here : as well as fixing various tests, I couldn't really defer the issue of Mesh printout, since I couldn't get this passing without doing something about that.

There is one possibly outstanding issue, regarding the printout of attributes... (separate comment follows)

@pp-mo pp-mo requested a review from lbdreyer January 19, 2022 15:22
@pp-mo
Copy link
Member Author

pp-mo commented Jan 19, 2022

Currently I am just printing attributes as a repr of a dict, and "hoping" it fits on a line
-- true for both _DimensionalMetadata things (mostly Coords), and the Mesh

@lbdreyer already suggested that this might be nasty in some cases (like long comment or history-type attributes)
In which case, output more like that of cube attributes would be better.
I think we can address that separately : I have some prototype code for it, and I will submit something if there is time.

@pp-mo pp-mo linked an issue Jan 19, 2022 that may be closed by this pull request
@lbdreyer lbdreyer self-assigned this Jan 20, 2022
Copy link
Member

@lbdreyer lbdreyer left a comment

Choose a reason for hiding this comment

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

I am still looking through the last bits of the mesh parts of the changes. In case I don't manage to finish it all today, I at least wanted to submit what I had.

Looking very good so far! Just a few minor comments I noticed while going through it

max_array_width=None,
):
"""
Make a printable text summary of a dimensional cube component.
Copy link
Member

Choose a reason for hiding this comment

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

"dimensional cube component" may be a bit confusing. We don't use that term anywhere and it's a bit hard to parse.
Instead, you could leave it as:
Make a printable text summary.
As this is a method on the AuxCoord, DimCoord, etc it may be clear enough to the user what object it is summarising.
or you could incorporate the name of the class?
Make a printable text summary of the DimCoord.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure of creating the string dynamically. It might work in Sphinx, or not ?
I will have a try ...

Copy link
Member Author

@pp-mo pp-mo Jan 21, 2022

Choose a reason for hiding this comment

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

No, I think it's not possible to produce a different docstring for each subclass without a metaclass type of operation (post-definition), which I think is too much fuss.

I see we do have some operations which describe themselves as 'dimensional metadata'. E.G. file:https:///net/home/h05/itpp/git/iris/iris_main/docs/src/_build/html/generated/api/iris/coords.html?highlight=dimensional%20metadata#iris.coords.CellMeasure.copy

But I think just leaving it un-named is easier, as you suggest.
I have just done that for now a2a6015

lib/iris/coords.py Show resolved Hide resolved
result += addline + f"shape: {shape_str}"

if self.has_bounds():
# line = f'bounds_shape: {self._bounds_dm.shape}'
Copy link
Member

Choose a reason for hiding this comment

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

I presume you forgot to remove this?

" long_name: 'enc'",
" cf_role: 'edge_node_connectivity'",
" start_index: 0",
" src_dim: 0",
Copy link
Member

Choose a reason for hiding this comment

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

I presume this will need to be updated once #4492 is merged in

Copy link
Member Author

@pp-mo pp-mo Jan 21, 2022

Choose a reason for hiding this comment

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

Yes, these will need changing in the MeshCoord.__str__ code as well.
Most other cases will happen 'automatically', as the generic code uses the metadata._fields to list the properties. But the relevant test results will still need fixing.

# Modify the generic 'default-form' result to produce what we want.
if shorten:
# Single-line form : insert the mesh+location before the array part
i_array = result.index("[")
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps the corneriest of corner cases but, if a long name included a "[" that would presumably cause problems here. Should we worry about that?

Copy link
Member Author

@pp-mo pp-mo Jan 25, 2022

Choose a reason for hiding this comment

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

Actually, the multiline case uses a search for 'location:', which in principle is also not solid.
I realised that I was repeating an old mistake here : this was the problem we had with the 'old' Cube.__str__ output, where repr_html was pulling it apart again and searching for certain pieces -- which was very fragile and troublesome.
So I have now added some crude (private) "structural information" output behaviour instead, to support the fiddling that MeshCoord.summary needs to do.

self.assertRegex(result, re_expected)

def test_alternative_location_and_axis(self):
meshcoord = sample_meshcoord(mesh=self.mesh, location="edge", axis="y")
result = str(meshcoord)
re_expected = r", location='edge', axis='y'"
# re_expected = r", location='edge', axis='y'"
Copy link
Member

Choose a reason for hiding this comment

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

Were you planning to removed this?

data_str = "[...]"

if self.has_bounds():
data_str += "+bnds"
Copy link
Member

Choose a reason for hiding this comment

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

Can I persuade you to change this to

Suggested change
data_str += "+bnds"
data_str += "+bounds"

It is only two characers after all, and I would prefer we use the official term "bounds" rather than invent a new term "bnds". Dropping the vowels may be natural and obvious to us but I would be concerned that this may trip some people up (non-native english speakers, perhaps?).

I think it's fine to use bnds to name a variable in some example code but not in part of the code in this way

For two vowels I would say it is worth being explicit

self.lat = iris.tests.stock.realistic_4d().coord("grid_latitude")[:10]
cube = iris.tests.stock.realistic_4d()
self.lat = cube.coord("grid_latitude")[:10]
self.height = cube.coord("level_height")[:10]
Copy link
Member Author

@pp-mo pp-mo Jan 25, 2022

Choose a reason for hiding this comment

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

These tests were a bit of a mess : in both the 'time' and 'nontime' classes, the 4 cases were 2 duplicates of only 2 things.
Unfortunately I'm not sure I've improved things much, though.
'height' is not actually an AuxCoord (class), though it is an aux-coord in the cube.
likewise, 'forecast_period' is not a brilliant example of a "time AuxCoord".

Given that this wasn't doing anything very useful before, I'm not going to spend any more effort on it.

@pp-mo
Copy link
Member Author

pp-mo commented Jan 25, 2022

Many thanks @lbdreyer .
I think I have now addressed all your outstanding points, but I did add a bit of complexity so you may like to consider that.

@pp-mo
Copy link
Member Author

pp-mo commented Jan 25, 2022

I think I have now addressed all your outstanding points, but I did add a bit of complexity so you may like to consider that.

Viewed from the morning after, I think the core routine is less clear now, and I'm sure that can be improved.
On the other hand, we may have more important concerns right now. I will spend a liitle time looking for some quick wins, but I will only submit any further changes if I think it is a clear win.

@pp-mo
Copy link
Member Author

pp-mo commented Jan 25, 2022

a liitle time looking for some quick wins

Ok, I think that is a fair bit clearer !

line("optional connectivities", 1)
for name, conn in optional_conns.items():
conn_string = conn.summary(shorten=True, linewidth=0)
line(f"{name}: {conn_string}", 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could there be a case for grouping these optional connectivities together by their location/source element. e.g.

edge:
edge_node_connectivity
(optionally)
edge_face_connectivity

face:
face_node_connectivity
(optionally)
face_edge_connectivity
face_face_connectivity

with boundary_node_connectivity as a special case on its own.
In this way, connectivities would be grouped with other connectivities which share their dimensions.
(As an aside, I don't believe we currently keep hold of the NetCDF dimension associated with boundary_node_connectivity, I'm not sure if this is something we should be concerned about. I had a look at what UGRID says about them and it seems to suggest that boundary_node_connectivity can have ancillary variables attached which I don't know if we support. This is probably not core functionality, but it might be worth recording as an issue for later.)
With that said, I could also imagine an argument that something like edge_face_connectivity is just as much assiciated with faces as it is edges. I'm not necessarily asking for a change, but I thought it would be worth at least raising the point for discussion.

Copy link
Member Author

@pp-mo pp-mo Jan 25, 2022

Choose a reason for hiding this comment

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

At present I am purposely listing the non-optional 'edge_node' and 'face_node' connectivities in their "element section", as you suggest.

In my view, the "essential" connectivities really have a different scope to the 'optional' ones, and should be treated differently.
I think If we put the 'optional' ones into their relevant sections, that's maybe not so clear.
In that case I think I would to list the required information first, and add the optional connectivities after the coords (if any). Whether they need to be labelled 'optional' I'm not quite so sure.

As examples from an actual file ...
CURRENT

Mesh : 'Topology data of 2D unstructured mesh'
    topology_dimension: 2
    node
        node_dimension: 'nMesh2d_half_levels_node'
        node coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55298,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55298,)>
    edge
        edge_dimension: 'nMesh2d_half_levels_edge'
        edge_node_connectivity: <Connectivity: Maps every edge/link to two nodes that it connects. / (unknown)  <lazy>  shape(110592, 2)>
        edge coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(110592,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(110592,)>
    face
        face_dimension: 'nMesh2d_half_levels_face'
        face_node_connectivity: <Connectivity: Maps every face to its corner nodes. / (unknown)  <lazy>  shape(55296, 4)>
        face coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55296,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55296,)>
    optional connectivities
        face_face_connectivity: <Connectivity: Indicates which other faces neighbor each face / (no_unit)  <lazy>  shape(55296, 4)>
        face_edge_connectivity: <Connectivity: Maps every face to its edges. / (unknown)  <lazy>  shape(55296, 4)>
        edge_face_connectivity: <Connectivity: neighbor faces for edges / (unknown)  <lazy>  shape(110592, 2)>
    long_name: 'Topology data of 2D unstructured mesh'
    var_name: 'Mesh2d_half_levels'

OPTIONALS IN OWN SECTIONS

Mesh : 'Topology data of 2D unstructured mesh'
    topology_dimension: 2
    node
        node_dimension: 'nMesh2d_half_levels_node'
        node coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55298,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55298,)>
    edge
        edge_dimension: 'nMesh2d_half_levels_edge'
        edge_node_connectivity: <Connectivity: Maps every edge/link to two nodes that it connects. / (unknown)  <lazy>  shape(110592, 2)>
        edge coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(110592,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(110592,)>
        edge_face_connectivity: <Connectivity: neighbor faces for edges / (unknown)  <lazy>  shape(110592, 2)>
    face
        face_dimension: 'nMesh2d_half_levels_face'
        face_node_connectivity: <Connectivity: Maps every face to its corner nodes. / (unknown)  <lazy>  shape(55296, 4)>
        face coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55296,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55296,)>
        face_face_connectivity: <Connectivity: Indicates which other faces neighbor each face / (no_unit)  <lazy>  shape(55296, 4)>
        face_edge_connectivity: <Connectivity: Maps every face to its edges. / (unknown)  <lazy>  shape(55296, 4)>
    long_name: 'Topology data of 2D unstructured mesh'
    var_name: 'Mesh2d_half_levels'

OPTIONALS LABELLED AS SUCH

Mesh : 'Topology data of 2D unstructured mesh'
    topology_dimension: 2
    node
        node_dimension: 'nMesh2d_half_levels_node'
        node coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55298,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55298,)>
    edge
        edge_dimension: 'nMesh2d_half_levels_edge'
        edge_node_connectivity: <Connectivity: Maps every edge/link to two nodes that it connects. / (unknown)  <lazy>  shape(110592, 2)>
        edge coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(110592,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(110592,)>
        optional connectivities
            edge_face_connectivity: <Connectivity: neighbor faces for edges / (unknown)  <lazy>  shape(110592, 2)>
    face
        face_dimension: 'nMesh2d_half_levels_face'
        face_node_connectivity: <Connectivity: Maps every face to its corner nodes. / (unknown)  <lazy>  shape(55296, 4)>
        face coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55296,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55296,)>
        optional connectivities
            face_face_connectivity: <Connectivity: Indicates which other faces neighbor each face / (no_unit)  <lazy>  shape(55296, 4)>
            face_edge_connectivity: <Connectivity: Maps every face to its edges. / (unknown)  <lazy>  shape(55296, 4)>
    long_name: 'Topology data of 2D unstructured mesh'
    var_name: 'Mesh2d_half_levels'

WITH BOUNDARIES

Mesh : 'Topology data of 2D unstructured mesh'
    topology_dimension: 2
    node
        node_dimension: 'nMesh2d_half_levels_node'
        node coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55298,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55298,)>
    edge
        edge_dimension: 'nMesh2d_half_levels_edge'
        edge_node_connectivity: <Connectivity: Maps every edge/link to two nodes that it connects. / (unknown)  <lazy>  shape(110592, 2)>
        edge coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(110592,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(110592,)>
        optional connectivities:
            edge_face_connectivity: <Connectivity: neighbor faces for edges / (unknown)  <lazy>  shape(110592, 2)>
    face
        face_dimension: 'nMesh2d_half_levels_face'
        face_node_connectivity: <Connectivity: Maps every face to its corner nodes. / (unknown)  <lazy>  shape(55296, 4)>
        face coordinates
            <AuxCoord: longitude / (degrees)  <lazy>  shape(55296,)>
            <AuxCoord: latitude / (degrees)  <lazy>  shape(55296,)>
        optional connectivities:
            face_face_connectivity: <Connectivity: Indicates which other faces neighbor each face / (no_unit)  <lazy>  shape(55296, 4)>
            face_edge_connectivity: <Connectivity: Maps every face to its edges. / (unknown)  <lazy>  shape(55296, 4)>
    boundary
        boundary_dimension: 'nMesh2d_half_levels_boundary'
        boundary_node_connectivity: <Connectivity: unknown / (unknown)  <lazy>  shape(1417, 2)>
    long_name: 'Topology data of 2D unstructured mesh'
    var_name: 'Mesh2d_half_levels'

Copy link
Member Author

@pp-mo pp-mo Jan 25, 2022

Choose a reason for hiding this comment

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

(After all that wordage)
I conclude that this change its perfectly possible.
If doing this, I think it makes sense to put boundaries in their own section, and report the 'boundary_dimension', like this. But in that case I think we should support it in the Mesh object too.

But I think there are some CONs too :

  • the existing output is more concise
  • the existing output is possibly slightly clearer
  • I'm not convinced that we really know what the "status" of boundaries is : it seems to be a feature that is not fully developed + it's not clear quite how it is intended to be used or what for

Copy link
Member

Choose a reason for hiding this comment

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

I agree that this is definitely a worthwhile thing to discuss. The hard part is knowing what the best option is! I'm strugglling to come to a conclusion on this. I'm not terribly familiar with the area myself so finding it hard to decide what a user would expect. However, I am leaning towards keeping things as they are , primarily because I like the conciseness of the original output, also @stephenworsley's point:

With that said, I could also imagine an argument that something like edge_face_connectivity is just as much assiciated with faces as it is edges.
This is an interesting point!

I think it might be simpler to keep the optional connectivities in their own section for now. But readdress this point at a later stage. Would you be okay with that @stephenworsley ?

Copy link
Member

Choose a reason for hiding this comment

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

Have checked with @stephenworsley offline. I will capture this point in an issue and we can address it (and come to a decision!) as a follow up activity. But I would like to merge this in as we have it

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think either way is workable and keeping it as it is currently is probably for the best for the moment. I imagine this could be something we could return to when we have a better idea of what to do with the boundaries.

Copy link
Member

@lbdreyer lbdreyer left a comment

Choose a reason for hiding this comment

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

Great! I'm finding the code in the summary function very clear and easy to follow what's going on so I think this is a big improvement!

All my comments have been addressed so as far as I am concerned this is good to go in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Usable printed output for Mesh objects Coords to print shorter, and avoid realising
4 participants