Skip to content

Optimize Face Edge Coordinate Construction  #1200

@philipc2

Description

@philipc2

Below are timings taken across multiple grid resolutions on a single NCAR Derecho CPU node.

Resolution Nodes Faces Edges Grid Load Time (s) face_edges_xyz (s)
30km 1,310,720 655,362 1,966,080 1.921 0.498
15km 5,242,880 2,621,442 7,864,320 7.458 2.213
7.5km 20,971,520 10,485,762 31,457,280 27.46 6.764
3.75km 83,886,080 41,943,042 125,829,120 113.993 28.683

While the performance here is not bad, it is noticeably slow for the higher resolutions.

@hongyuchen1030 pointed out that we could consider caching this variable for re-use, especially since multiple calls to zonal average could take advantage of this. There is an informative discussion about this in #1180

The current implementation only relies on the face_node_connectivity and uses it to derive the edge information. If the edge_node_connectivity and face_edge_connectivity are available, it may be more efficient to use these to index our coordinates.

def _get_cartesian_face_edge_nodes(
face_node_conn, n_face, n_max_face_edges, node_x, node_y, node_z
):
"""Construct an array to hold the edge Cartesian coordinates connectivity
for multiple faces in a grid.
Parameters
----------
face_node_conn : np.ndarray
An array of shape (n_face, n_max_face_edges) containing the node indices for each face. Accessed through `grid.face_node_connectivity.value`.
n_face : int
The number of faces in the grid. Accessed through `grid.n_face`.
n_max_face_edges : int
The maximum number of edges for any face in the grid. Accessed through `grid.n_max_face_edges`.
node_x : np.ndarray
An array of shape (n_nodes,) containing the x-coordinate values of the nodes. Accessed through `grid.node_x`.
node_y : np.ndarray
An array of shape (n_nodes,) containing the y-coordinate values of the nodes. Accessed through `grid.node_y`.
node_z : np.ndarray
An array of shape (n_nodes,) containing the z-coordinate values of the nodes. Accessed through `grid.node_z`.
Returns
-------
face_edges_cartesian : np.ndarray
An array of shape (n_face, n_max_face_edges, 2, 3) containing the Cartesian coordinates of the edges
for each face. It might contain dummy values if the grid has holes.
Examples
--------
>>> face_node_conn = np.array(
... [
... [0, 1, 2, 3, 4],
... [0, 1, 3, 4, INT_FILL_VALUE],
... [0, 1, 3, INT_FILL_VALUE, INT_FILL_VALUE],
... ]
... )
>>> n_face = 3
>>> n_max_face_edges = 5
>>> node_x = np.array([0, 1, 1, 0, 1, 0])
>>> node_y = np.array([0, 0, 1, 1, 2, 2])
>>> node_z = np.array([0, 0, 0, 0, 1, 1])
>>> _get_cartesian_face_edge_nodes(
... face_node_conn, n_face, n_max_face_edges, node_x, node_y, node_z
... )
array([[[[ 0, 0, 0],
[ 1, 0, 0]],
[[ 1, 0, 0],
[ 1, 1, 0]],
[[ 1, 1, 0],
[ 0, 1, 0]],
[[ 0, 1, 0],
[ 1, 2, 1]],
[[ 1, 2, 1],
[ 0, 0, 0]]],
[[[ 0, 0, 0],
[ 1, 0, 0]],
[[ 1, 0, 0],
[ 0, 1, 0]],
[[ 0, 1, 0],
[ 1, 2, 1]],
[[ 1, 2, 1],
[ 0, 0, 0]],
[[INT_FILL_VALUE, INT_FILL_VALUE, INT_FILL_VALUE],
[INT_FILL_VALUE, INT_FILL_VALUE, INT_FILL_VALUE]]],
[[[ 0, 0, 0],
[ 1, 0, 0]],
[[ 1, 0, 0],
[ 0, 1, 0]],
[[ 0, 1, 0],
[ 0, 0, 0]],
[[INT_FILL_VALUE, INT_FILL_VALUE, INT_FILL_VALUE],
[INT_FILL_VALUE, INT_FILL_VALUE, INT_FILL_VALUE]],
[[INT_FILL_VALUE, INT_FILL_VALUE, INT_FILL_VALUE],
[INT_FILL_VALUE, INT_FILL_VALUE, INT_FILL_VALUE]]]])
"""
# Shift node connections to create edge connections
face_node_conn_shift = np.roll(face_node_conn, -1, axis=1)
# Construct edge connections by combining original and shifted node connections
face_edge_conn = np.array([face_node_conn, face_node_conn_shift]).T.swapaxes(0, 1)
# swap the first occurrence of INT_FILL_VALUE with the last value in each sub-array
face_edge_conn = _swap_first_fill_value_with_last(face_edge_conn)
# Get the indices of the nodes from face_edge_conn
face_edge_conn_flat = face_edge_conn.reshape(-1)
valid_mask = face_edge_conn_flat != INT_FILL_VALUE
# Get the valid node indices
valid_edges = face_edge_conn_flat[valid_mask]
# Create an array to hold the Cartesian coordinates of the edges
face_edges_cartesian = np.full(
(len(face_edge_conn_flat), 3), INT_FILL_VALUE, dtype=float
)
# Fill the array with the Cartesian coordinates of the edges
face_edges_cartesian[valid_mask, 0] = node_x[valid_edges]
face_edges_cartesian[valid_mask, 1] = node_y[valid_edges]
face_edges_cartesian[valid_mask, 2] = node_z[valid_edges]
return face_edges_cartesian.reshape(n_face, n_max_face_edges, 2, 3)

Metadata

Metadata

Assignees

Labels

scalabilityRelated to scalability & performance efforts

Type

No type

Projects

Status

🏗 In progress

Relationships

None yet

Development

No branches or pull requests

Issue actions