iris: Saving a cube with a mesh does not include `coordinate` attribute, as required by UGrid conventions

šŸ› Bug Report

It looks like the mesh support in iris is not setting the coordinates attribute for the data variables correctly. Worse, when working with a source data file that does have the correct attributes, these are being lost in the save. This is causing an issue when trying to load the resulting files into LFRic.

How To Reproduce

Steps to reproduce the behaviour:

In [1]: import iris

In [2]: # Using scitools default version – same behaviour is seen with default-next too.

In [3]: iris.__file__
Out[3]: '.../default-2022_11_22/lib/python3.9/site-packages/iris/__init__.py'

In [4]: # Example data does have coordinate attributes for the data variables:

In [5]: !ncdump -h ./lib/ants/tests/resources/stock/data_C4.nc | grep 'coordinates'
           sample_data:coordinates = "example_C4_face_x example_C4_face_y" ;
           additional_sample_data:coordinates = "example_C4_face_x example_C4_face_y" ;
           example_C4:node_coordinates = "example_C4_node_y example_C4_node_x" ;
           example_C4:face_coordinates = "example_C4_face_y example_C4_face_x" ;

In [6]: # (First two results are attributes on data variables, second two are for topology attributes)

In [7]: from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD

In [8]: with PARSE_UGRID_ON_LOAD.context():
   ...:     cubes = iris.load('./lib/ants/tests/resources/stock/data_C4.nc')
   ...: 

In [9]: iris.save(cubes, 'result.nc')

In [10]: # Notice that the coordinate attributes have been dropped from the data variables

In [11]: !ncdump -h result.nc | grep 'coordinates'
           example_C4:node_coordinates = "example_C4_node_x example_C4_node_y" ;
           example_C4:face_coordinates = "example_C4_face_x example_C4_face_y" ;

In [12]: # Full ncdump for completeness

In [13]: !ncdump -h result.nc
netcdf result {
dimensions:
     num_node = 98 ;
     dim0 = 96 ;
     example_C4_face_N_nodes = 4 ;
variables:
     int example_C4 ;
           example_C4:cf_role = "mesh_topology" ;
           example_C4:topology_dimension = 2 ;
           example_C4:long_name = "Topology data of 2D unstructured mesh" ;
           example_C4:node_coordinates = "example_C4_node_x example_C4_node_y" ;
           example_C4:face_coordinates = "example_C4_face_x example_C4_face_y" ;
           example_C4:face_node_connectivity = "face_nodes" ;
     double example_C4_node_x(num_node) ;
           example_C4_node_x:units = "degrees_east" ;
           example_C4_node_x:standard_name = "longitude" ;
           example_C4_node_x:long_name = "longitude of 2D mesh nodes." ;
     double example_C4_node_y(num_node) ;
           example_C4_node_y:units = "degrees_north" ;
           example_C4_node_y:standard_name = "latitude" ;
           example_C4_node_y:long_name = "latitude of 2D mesh nodes." ;
     double example_C4_face_x(dim0) ;
           example_C4_face_x:units = "degrees_east" ;
           example_C4_face_x:standard_name = "longitude" ;
           example_C4_face_x:long_name = "longitude of 2D face centres" ;
     double example_C4_face_y(dim0) ;
           example_C4_face_y:units = "degrees_north" ;
           example_C4_face_y:standard_name = "latitude" ;
           example_C4_face_y:long_name = "latitude of 2D face centres" ;
     int face_nodes(dim0, example_C4_face_N_nodes) ;
           face_nodes:long_name = "Maps every quadrilateral face to its four corner nodes." ;
           face_nodes:Conventions = "UGRID-1.0" ;
           face_nodes:cf_role = "face_node_connectivity" ;
           face_nodes:start_index = 1 ;
     double sample_data(dim0) ;
           sample_data:long_name = "sample_data" ;
           sample_data:units = "1" ;
           sample_data:mesh = "example_C4" ;
           sample_data:location = "face" ;
     double additional_sample_data(dim0) ;
           additional_sample_data:long_name = "additional_sample_data" ;
           additional_sample_data:units = "1" ;
           additional_sample_data:mesh = "example_C4" ;
           additional_sample_data:location = "face" ;

// global attributes:
           :online_operation = "once" ;
           :Conventions = "CF-1.7" ;
}

Expected behaviour

Saving a cube with a mesh should set the coordinates attribute for the data variables, as per: http://ugrid-conventions.github.io/ugrid-conventions/#data-variables

Environment

  • OS & Version: RHEL7
  • Iris Version: 3.3.1 and 3.4.0

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 20 (18 by maintainers)

Most upvoted comments

Thanks for the detail @arjclark. I just talked through a hypothetical with @hdyson that helped me break the link between this and roundtripping concerns…

During development we had interpreted the redundancy as meaning it didn’t matter about the data variable so long as there were attributes in the mesh variable. But the interpretation in this issue is that the referenced section of the UGRID docs is THE correct way things should be.

That would mean that: even if Iris loaded a file where edge/face coordinates were only referenced as attributes on the mesh variable, Iris should also add these as attributes on the data variable when saving. This sounds like a defensible idea to me, and simpler to implement than any of my previous suggestions.

I really think this one has run its course and we can’t see the wood for the trees now. I’m going to close this ticket and open up a new issue with the iris specific parts of this, and let the conventions conversations carry on in the UGrid repository.

UGrid compliant files that are not CF compliant

Yes that scared me a bit too.
I have posted a bit of a request for clarification

I would certainly hope + expect Iris output to be both CF and UGRID compliant. We have an issue to include UGRID in the Conventions declaration #5030

I just put something very similar to this in an email to @pp-mo, so apologies for the redundancy but I think it’s worth flagging somewhere more public. In the linked UGrid issue: https://github.com/ugrid-conventions/ugrid-conventions/issues/63 there’s discussion of the potential of UGrid compliant files that are not CF compliant.

My personal opinion is that iris should aim to be writing mesh cubes that are both UGrid and CF compliant - I think the inconsistency between CF compliant times/vertical levels/etc in the same file as CF non-compliant meshes would lead to user confusion.

@arjclark What we do need to do is produce files that are CF compliant … saves the file in a UGRID-conformant format

@pp-mo I think the result is CF (and UGRID) compliant.

Following offline conversation with @hdyson, I’m coming around to this view. In which case, it will make sense for Iris to

  1. issue a warning if this connection is ā€œmissingā€ in input
  2. include relevant mesh coordinates in every data variable ā€œcoordinatesā€ attributes.

This also feeds back into the nascent conformance rules and the ugrid-checker