You are viewing the latest unreleased documentation v3.2.2.dev178. You may prefer a stable version.

# The Mesh Data Model#

Important

This page is intended to summarise the essentials that Iris users need to know about meshes. For exhaustive details on UGRID itself: visit the official UGRID conventions site.

## Evolution, not revolution#

Mesh support has been designed wherever possible to fit within the existing Iris model. Meshes concern only the spatial geography of data, and can optionally be limited to just the horizontal geography (e.g. X and Y). Other dimensions such as time or ensemble member (and often vertical levels) retain their familiar structured format.

The UGRID conventions themselves are designed as an addition to the existing CF conventions, which are at the core of Iris’ philosophy.

## What’s Different?#

The mesh format represents data’s geography using an unstructured mesh. This has significant pros and cons when compared to a structured grid.

### The Detail#

#### Structured Grids (the old world)#

Assigning data to locations using a structured grid is essentially an act of matching coordinate arrays to each dimension of the data array. The data can also be represented as an area (instead of a point) by including a bounds array for each coordinate array. Figure 1 visualises an example. Figure 1 Data on a structured grid.# 1D coordinate arrays (pink circles) are combined to construct a structured grid of points (pink crosses). 2D bounds arrays (blue circles) can also be used to describe the 1D boundaries (blue lines) at either side of each rank of points; each point therefore having four bounds (x+y, upper+lower), together describing a quadrilateral area around that point. Data from the 2D data array (orange circles) can be assigned to these point locations (orange diamonds) or area locations (orange quads) by matching the relative positions in the data array to the relative spatial positions - see the black outlined shapes as examples of this in action.

#### Unstructured Meshes (the new world)#

A mesh is made up of different types of element:

 0D `node` The ‘core’ of the mesh. A point position in space, constructed from 2 or 3 coordinates (2D or 3D space). 1D `edge` Constructed by connecting 2 nodes. 2D `face` Constructed by connecting 3 or more nodes. 3D `volume` Constructed by connecting 4 or more nodes (which must each have 3 coordinates - 3D space).

Every node in the mesh is defined by indexing the 1-dimensional X and Y (and optionally Z) coordinate arrays (the `node_coordinates`) - e.g. `(x, y)` gives the position of the fourth node. Note that this means each node has its own coordinates, independent of every other node.

Any higher dimensional element - an edge/face/volume - is described by a sequence of the indices of the nodes that make up that element. E.g. a triangular face made from connecting the first, third and fourth nodes: `[0, 2, 3]`. These 1D sequences combine into a 2D array enumerating all the elements of that type - edge/face/volume - called a connectivity. E.g. we could make a mesh of 4 nodes, with 2 triangles described using this `face_node_connectivity`: `[[0, 2, 3], [3, 2, 1]]` (note the shared nodes).

Note

More on Connectivities:

• The element type described by a connectivity is known as its location; `edge` in `edge_node_connectivity`.

• According to the UGRID conventions, the nodes in a face should be listed in “anti-clockwise order from above”.

• Connectivities also exist to connect the higher dimensional elements, e.g. `face_edge_connectivity`. These are optional conveniences to speed up certain operations and will not be discussed here.

Important

Meshes are unstructured. The mesh elements - represented in the coordinate and connectivity arrays detailed above - are enumerated along a single unstructured dimension. An element’s position along this dimension has nothing to do with its spatial position.

A data variable associated with a mesh has a location of either `node`, `edge`, `face` or `volume`. The data is stored in a 1D array with one datum per element, matched to its element by matching the datum index with the coordinate or connectivity index along the unstructured dimension. So for an example data array called `foo`: `foo` would be at position `(x, y)` if it were node-located, or at `faces` if it were face-located. Figure 2 visualises an example of what is described above. Figure 2 Data on an unstructured mesh# 1D coordinate arrays (pink circles) describe node positions in space (pink crosses). A 2D connectivity array (blue circles) describes faces by connecting four nodes - by referencing their indices - into a face outline (blue outlines on the map). Data from the 1D data array (orange circles) can be assigned to these node locations (orange diamonds) or face locations (orange quads) by matching the indices in the data array to the indices in the coordinate arrays (for nodes) or connectivity array (for faces). See the black outlined shapes as examples of index matching in action, and the black stippled shapes to demonstrate that relative array position confers no relative spatial information.

The mesh model also supports edges/faces/volumes having associated ‘centre’ coordinates - to allow point data to be assigned to these elements. ‘Centre’ is just a convenience term - the points can exist anywhere within their respective elements. See Figure 3 for a visualised example. Figure 3 Data can be assigned to mesh edge/face/volume ‘centres’# 1D node coordinate arrays (pink circles) describe node positions in space (pink crosses). A 2D connectivity array (blue circles) describes faces by connecting four nodes into a face outline (blue outlines on the map). Further 1D face coordinate arrays (pink circles) describe a ‘centre’ point position (pink stars) for each face enumerated in the connectivity array.
##### Mesh Flexibility#

Above we have seen how one could replicate data on a structured grid using a mesh instead. But the utility of a mesh is the extra flexibility it offers. Here are the main examples:

Every node is completely independent - every one can have unique X andY (and Z) coordinate values. See Figure 4. Figure 4 Every mesh node is completely independent# The same array shape and structure used to describe the node positions (pink crosses) in a regular grid (left-hand maps) is equally able to describe any position for these nodes (e.g. the right-hand maps), simply by changing the array values. The quadrilateral faces (blue outlines) can therefore be given any quadrilateral shape by re-positioning their constituent nodes.

Faces and volumes can have variable node counts, i.e. different numbers of sides. This is achieved by masking the unused ‘slots’ in the connectivity array. See Figure 5. Figure 5 Mesh faces can have different node counts (using masking)# The 2D connectivity array (blue circles) describes faces by connecting nodes (pink crosses) to make up a face (blue outlines). The faces can use different numbers of nodes by shaping the connectivity array to accommodate the face with the most nodes, then masking unused node ‘slots’ (black circles) for faces with fewer nodes than the maximum.

Data can be assigned to lines (edges) just as easily as points (nodes) or areas (faces). See Figure 6. Figure 6 Data can be assigned to mesh edges# The 2D connectivity array (blue circles) describes edges by connecting 2 nodes (pink crosses) to make up an edge (blue lines). Data can be assigned to the edges (orange lines) by matching the indices of the 1D data array (not shown) to the indices in the connectivity array.

### What does this mean?#

#### Meshes can represent much more varied spatial arrangements#

The highly specific way of recording position (geometry) and shape (topology) allows meshes to represent essentially any spatial arrangement of data. There are therefore many new applications that aren’t possible using a structured grid, including:

#### Mesh ‘payload’ is much larger than with structured grids#

Coordinates are recorded per-node, and connectivities are recorded per-element. This is opposed to a structured grid, where a single coordinate value is shared by every data point/area along that line.

For example: representing the surface of a cubed-sphere using a mesh leads to coordinates and connectivities being ~8 times larger than the data itself, as opposed to a small fraction of the data size when dividing a spherical surface using a structured grid of longitudes and latitudes.

This further increases the emphasis on lazy loading and processing of data using packages such as Dask.

Note

The large, 1D data arrays associated with meshes are a very different shape to what Iris users and developers are used to. It is suspected that optimal performance will need new chunking strategies, but at time of writing (`Jan 2022`) experience is still limited.

#### Spatial operations on mesh data are more complex#

Detail: Working with Mesh Data

Indexing a mesh data array cannot be used for:

1. Region selection

2. Neighbour identification

This is because - unlike with a structured data array - relative position in a mesh’s 1-dimensional data arrays has no relation to relative position in space. We must instead perform specialised operations using the information in the mesh’s connectivities, or by translating the mesh into a format designed for mesh analysis such as VTK.

Such calculations can still be optimised to avoid them slowing workflows, but the important take-away here is that adaptation is needed when working mesh data.

## How Iris Represents This#

Remember this is a prose summary. Precise documentation is at: `iris.experimental.ugrid`.

Note

At time of writing (`Jan 2022`), neither 3D meshes nor 3D elements (volumes) are supported.

### The Basics#

The Iris `Cube` has several new members:

These members will all be `None` for a `Cube` with no associated `Mesh`.

This `Cube`'s unstructured dimension has multiple attached `iris.experimental.ugrid.MeshCoord`s (one for each axis e.g. `x`/`y`), which can be used to infer the points and bounds of any index on the `Cube`'s unstructured dimension.

```>>> print(edge_cube)
edge_data / (K)                     (-- : 6; height: 3)
Dimension coordinates:
height                          -          x
Mesh coordinates:
latitude                        x          -
longitude                       x          -
Mesh:
name                        my_mesh
location                    edge

>>> print(edge_cube.location)
edge

>>> print(edge_cube.mesh_dim())
0

>>> print(edge_cube.mesh.summary(shorten=True))
<Mesh: 'my_mesh'>
```

### The Detail#

#### How UGRID information is stored#

```>>> print(edge_cube.mesh)
Mesh : 'my_mesh'
topology_dimension: 2
node
node_dimension: 'Mesh2d_node'
node coordinates
<AuxCoord: longitude / (degrees_east)  [...]  shape(5,)>
<AuxCoord: latitude / (degrees_north)  [...]  shape(5,)>
edge
edge_dimension: 'Mesh2d_edge'
edge_node_connectivity: <Connectivity: unknown / (unknown)  [...]  shape(6, 2)>
edge coordinates
<AuxCoord: longitude / (degrees_east)  [...]  shape(6,)>
<AuxCoord: latitude / (degrees_north)  [...]  shape(6,)>
face
face_dimension: 'Mesh2d_face'
face_node_connectivity: <Connectivity: unknown / (unknown)  [...]  shape(2, 4)>
face coordinates
<AuxCoord: longitude / (degrees_east)  [...]  shape(2,)>
<AuxCoord: latitude / (degrees_north)  [...]  shape(2,)>
long_name: 'my_mesh'
```

#### MeshCoords#

Links a `Cube` to a `Mesh` by attaching to the `Cube`'s unstructured dimension, in the same way that all `Coord`s attach to `Cube` dimensions. This allows a single `Cube` to have a combination of unstructured and structured dimensions (e.g. horizontal mesh plus vertical levels and a time series), using the same logic for every dimension.

`MeshCoord`s are instantiated using a given `Mesh`, `location` (“node”/”edge”/”face”) and `axis`. The process interprets the `Mesh`'s `node_coords` and if appropriate the `edge_node_connectivity`/ `face_node_connectivity` and `edge_coords`/ `face_coords` to produce a `Coord` `points` and `bounds` representation of all the `Mesh`'s nodes/edges/faces for the given axis.

The method `iris.experimental.ugrid.Mesh.to_MeshCoords()` is available to create a `MeshCoord` for every axis represented by that `Mesh`, given only the `location` argument

```>>> for coord in edge_cube.coords(mesh_coords=True):
...     print(coord)
MeshCoord :  latitude / (degrees_north)
mesh: <Mesh: 'my_mesh'>
location: 'edge'
points: [3. , 1.5, 1.5, 1.5, 0. , 0. ]
bounds: [
[3., 3.],
[3., 0.],
[3., 0.],
[3., 0.],
[0., 0.],
[0., 0.]]
shape: (6,)  bounds(6, 2)
dtype: float64
standard_name: 'latitude'
axis: 'y'
MeshCoord :  longitude / (degrees_east)
mesh: <Mesh: 'my_mesh'>
location: 'edge'
points: [2.5, 0. , 5. , 6.5, 2.5, 6.5]
bounds: [
[0., 5.],
[0., 0.],
[5., 5.],
[5., 8.],
[0., 5.],
[5., 8.]]
shape: (6,)  bounds(6, 2)
dtype: float64
standard_name: 'longitude'
axis: 'x'
```