Topological Aggregations#

Data variables are typically mapped to either the nodes, edges, or faces of an unstructured grid. The data on each of these elements can be manipulated and aggregated to perform various operations, such as mean, min, max and many others. This section will introduce the concept of Topological Aggregations and how to perform them using UXarray.

import uxarray as ux

What are Aggregations?#

An aggregation is an operation that processes data and returns a summarized output. In the context of Numpy, this includes functions such as:

  • np.mean(): Calculate the average value from an array of elements

  • np.min(): Calculate the minimum value from an array of elements

  • np.std(): Calculate the standard deviation from an array of elements

In the context of a one-dimensional array, the aggregation is performed over the entire array. Otherwise, it is performed across a specific axis.

What are Topological Aggregations?#

When working with unstructured grids, data variables are mapped to either nodes, edges, or faces and stored as one-dimensional slices in memory, with no spatial relationship between neighbors. This means that performing a regular aggregation as discussed above would not consider the topology of the grid elements.

A topological aggregation can be thought of as performing multiple aggregations on a per-element basis. For example, instead of computing the average across all values, we can compute the average of all the nodes that surround each face and store the result on each face.

By utilizing connectivity information, we can perform the following topological aggregations:

  • Node to Face: Applied to the nodes that surround each face

  • Node to Edge: Applied to the nodes that saddle each edge

  • Edge to Node: Applied to the edges that saddle each node

  • Edge to Face: Applied to the edges that surround each face

  • Face to Node: Applied to the faces that surround each node

  • Face to Edge: Applied to the faces that saddle each edge

UXarray supports the following topological aggregation functions:

  • UxDataArray.topological_mean()

  • UxDataArray.topological_max()

  • UxDataArray.topological_min()

  • UxDataArray.topological_prod()

  • UxDataArray.topological_sum()

  • UxDataArray.topological_std()

  • UxDataArray.topological_var()

  • UxDataArray.topological_median()

  • UxDataArray.topological_all()

  • UxDataArray.topological_any()

Each of these aggregations performs the same operation described in Numpy, but is applied on a per-element basis.

For the remainder of this guide, we will be using the topological_mean aggregation, but can be swapped for any of the above methods if desired.

Data#

The data used in this section is the quad hexagon mesh, with three random data variables mapped to the nodes, edges, and faces.

grid_path = "../../test/meshfiles/ugrid/quad-hexagon/grid.nc"

data_paths = [
    "../../test/meshfiles/ugrid/quad-hexagon/random-node-data.nc",
    "../../test/meshfiles/ugrid/quad-hexagon/random-edge-data.nc",
    "../../test/meshfiles/ugrid/quad-hexagon/random-face-data.nc",
]

uxds = ux.open_mfdataset(grid_path, data_paths)

uxds
<xarray.UxDataset> Size: 312B
Dimensions:           (n_node: 16, n_edge: 19, n_face: 4)
Dimensions without coordinates: n_node, n_edge, n_face
Data variables:
    random_data_node  (n_node) float64 128B dask.array<chunksize=(16,), meta=np.ndarray>
    random_data_edge  (n_edge) float64 152B dask.array<chunksize=(19,), meta=np.ndarray>
    random_data_face  (n_face) float64 32B dask.array<chunksize=(4,), meta=np.ndarray>

We can visualize the data on each element by using different markers:

(
    uxds.uxgrid.plot(line_color="black")
    * uxds["random_data_node"]
    .plot.points(
        cmap="inferno", size=150, marker="circle", clabel=None, tools=["hover"]
    )
    .relabel("Node Data")
    * uxds["random_data_edge"]
    .plot.points(
        cmap="inferno", size=150, marker="square", clabel=None, tools=["hover"]
    )
    .relabel("Edge Data")
    * uxds["random_data_face"]
    .plot.points(
        cmap="inferno", size=150, marker="triangle", clabel=None, tools=["hover"]
    )
    .relabel("Face Data")
).opts(legend_position="top_right")

Node Aggregations#

The follow aggregations are for node-centered data.

Node to Face#

We can aggregate the data from the nodes that surround each face and store the result on each face.

Optional Alt Text
uxda_node_face_agg = uxds["random_data_node"].topological_mean(destination="face")
(
    uxds.uxgrid.plot(line_color="black")
    * uxds["random_data_node"]
    .plot.points(
        cmap="inferno", size=150, marker="circle", clabel=None, tools=["hover"]
    )
    .relabel("Node Data")
    * uxda_node_face_agg.plot.points(
        cmap="inferno", size=150, marker="triangle", clabel=None, tools=["hover"]
    ).relabel("Node to Face Mean")
).opts(title="Node to Face Aggregation (Mean)", legend_position="top_right")

One use case for aggregating node-centered data to each face is that it allows for the result to be plotted as Polygons.

uxda_node_face_agg.plot.polygons(
    cmap="inferno",
    title="Polygon Plot of Node to Face Aggregation (Mean)",
    tools=["hover"],
).opts(title="Node to Face Aggregation (Mean)")