Graph & Connectivity#
Derived connectivity tables, graph traversal, and binary morphology on a mesh.
Everything here is pure: each function reads a Mesh2d and returns new arrays or a new
Connectivity, never writing a derived table back onto the mesh it was handed. The edge tables are
derived from the faces; when a file declares its own edge_node_connectivity those edges keep their
positions (edge-located variables index into them) and any face-implied edge the file omits is
appended after them, so a partial edge list still yields a complete face topology.
sparse_adjacency is the shared primitive: connected_components, reverse_cuthill_mckee and the
binary morphology all read it.
One morphology pass is simultaneous — the neighbour test is taken from the mask as it stands at the
start of the pass, and the border_value imputation joins that same update — so one iteration moves
exactly one ring, matching scipy.ndimage.
pyramids.netcdf.ugrid.graph
#
Mesh graph and connectivity operations for UGRID 2D meshes.
A collection of pure functions that each take a :class:~pyramids.netcdf.ugrid.mesh.Mesh2d
(and/or plain numpy arrays) and return plain results — numpy arrays, :class:Connectivity
wrappers, scipy sparse matrices, shapely geometries, or a rebuilt Mesh2d for the one
topology-transforming helper. None of them mutate their input.
The module groups the graph-flavoured mesh operations behind a single import:
- Connectivity builders that pyramids does not derive today:
node_node_connectivity,node_face_connectivity,node_edge_connectivity,edge_node_connectivity,edge_face_connectivity,face_edge_connectivityandedge_edge_connectivity. sparse_adjacency— a scipy sparse adjacency matrix over faces or nodes, the shared primitive the graph algorithms below build on.- Facet remapping (
to_node/to_edge/to_face) that averages a per-element field onto another mesh facet via connectivity. - Graph algorithms:
connected_components,reverse_cuthill_mckee(+permute_faces),binary_dilation/binary_erosion,voronoi/circumcentersand the boundary helpersexterior_edges/exterior_faces/bounding_polygon.
scipy (scipy.sparse, scipy.sparse.csgraph, scipy.spatial) and shapely are hard
dependencies of the UGRID subsystem, so they are imported at module top.
node_face_connectivity(mesh)
#
Faces incident to each node (inverse of face_node_connectivity).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
Ragged |
Source code in src/pyramids/netcdf/ugrid/graph.py
edge_node_connectivity(mesh)
#
The two nodes of each edge, derived from the faces when absent.
A row the file declared but never defined -- one padded with the fill value, or a repeat of
an edge already listed -- comes back as a (-1, -1) placeholder. It keeps its
position, so an edge-located variable stays aligned with the table, but it describes no
edge: :func:_real_edges is the mask of the rows that do, and
:func:edge_face_connectivity / :func:edge_edge_connectivity answer an all-fill row at
the same positions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
|
Connectivity
|
file's edge list declared an edge it did not define. |
Source code in src/pyramids/netcdf/ugrid/graph.py
node_edge_connectivity(mesh)
#
Edges incident to each node (inverse of edge_node_connectivity).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
Ragged |
Source code in src/pyramids/netcdf/ugrid/graph.py
node_node_connectivity(mesh)
#
Neighbouring nodes of each node, derived from the mesh edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
Ragged |
Source code in src/pyramids/netcdf/ugrid/graph.py
edge_face_connectivity(mesh)
#
The (up to two) faces on either side of each edge.
Edges on the mesh boundary carry a -1 fill on their missing side.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
|
Source code in src/pyramids/netcdf/ugrid/graph.py
face_edge_connectivity(mesh)
#
The boundary edges of each face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
Ragged |
Source code in src/pyramids/netcdf/ugrid/graph.py
edge_edge_connectivity(mesh)
#
Edges that share a node with each edge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Connectivity |
Connectivity
|
Ragged |
Source code in src/pyramids/netcdf/ugrid/graph.py
sparse_adjacency(mesh, kind='face')
#
Build a symmetric scipy sparse adjacency matrix for the mesh.
For kind="face" two faces are adjacent when they share an edge (an edge whose
edge_face_connectivity has two valid faces). For kind="node" two nodes are
adjacent when an edge joins them. The result is the shared primitive consumed by
:func:connected_components, :func:reverse_cuthill_mckee and the binary morphology.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
kind
|
str
|
|
'face'
|
Returns:
| Type | Description |
|---|---|
Any
|
scipy.sparse.csr_matrix: The symmetric |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pyramids/netcdf/ugrid/graph.py
to_node(mesh, values, source='face')
#
Remap a per-face or per-edge field onto the nodes by averaging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
values
|
NDArray
|
Source field, element axis last. |
required |
source
|
str
|
|
'face'
|
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: The node-located field. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pyramids/netcdf/ugrid/graph.py
to_face(mesh, values, source='node')
#
Remap a per-node or per-edge field onto the faces by averaging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
values
|
NDArray
|
Source field, element axis last. |
required |
source
|
str
|
|
'node'
|
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: The face-located field. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pyramids/netcdf/ugrid/graph.py
to_edge(mesh, values, source='node')
#
Remap a per-node or per-face field onto the edges by averaging.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
values
|
NDArray
|
Source field, element axis last. |
required |
source
|
str
|
|
'node'
|
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: The edge-located field. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pyramids/netcdf/ugrid/graph.py
connected_components(mesh)
#
Label each face by the connected component it belongs to.
Two faces are in the same component when a path of shared edges joins them — the mesh analogue of a raster cluster labelling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: Per-face component label of length |
Source code in src/pyramids/netcdf/ugrid/graph.py
reverse_cuthill_mckee(mesh)
#
Return a face permutation that reduces the face-adjacency bandwidth.
Computes the reverse Cuthill-McKee ordering of the face adjacency. Apply it to the mesh
with :func:permute_faces and to every face-located variable with arr[..., perm].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: A permutation of |
Source code in src/pyramids/netcdf/ugrid/graph.py
permute_faces(mesh, perm)
#
Rebuild a mesh with its faces reordered by perm.
Node coordinates and numbering are unchanged — only the face rows (and any face centre
coordinates) are reordered — so the result is congruent to the input. New face k is
old face perm[k]; the input mesh is not mutated, and no array is shared with it.
The three face-indexed tables -- face_edge_connectivity, face_face_connectivity
and edge_face_connectivity -- are dropped rather than permuted, since their
contents index the old face numbering. Derive them again from the result if you need
them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to reorder. |
required |
perm
|
NDArray
|
A permutation of |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Mesh2d |
Mesh2d
|
The reordered mesh. |
Source code in src/pyramids/netcdf/ugrid/graph.py
binary_dilation(mesh, mask, iterations=1, border_value=False, fixed=None)
#
Grow a True region of a boolean face mask across shared edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
mask
|
NDArray
|
Input boolean face mask (not mutated). |
required |
iterations
|
int
|
Number of rings to grow. |
1
|
border_value
|
bool
|
Value imputed to the mesh exterior (grows inward from the boundary
when |
False
|
fixed
|
NDArray | None
|
Optional boolean faces held at their input value. |
None
|
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: The dilated boolean face mask. |
Source code in src/pyramids/netcdf/ugrid/graph.py
binary_erosion(mesh, mask, iterations=1, border_value=False, fixed=None)
#
Shrink a True region of a boolean face mask across shared edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
mask
|
NDArray
|
Input boolean face mask (not mutated). |
required |
iterations
|
int
|
Number of rings to shrink. |
1
|
border_value
|
bool
|
Value imputed to the mesh exterior (erodes from the boundary when
|
False
|
fixed
|
NDArray | None
|
Optional boolean faces held at their input value. |
None
|
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: The eroded boolean face mask. |
Source code in src/pyramids/netcdf/ugrid/graph.py
circumcenters(mesh)
#
Per-face circumcenter coordinates for a triangular mesh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read (every face must be a triangle). |
required |
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the mesh is not purely triangular. |
Source code in src/pyramids/netcdf/ugrid/graph.py
voronoi(mesh)
#
Voronoi tessellation of the mesh nodes (the mesh dual).
Uses :class:scipy.spatial.Voronoi over the node coordinates. For a triangular mesh that
is Delaunay the Voronoi vertices coincide with the face :func:circumcenters, and interior
nodes receive bounded dual cells while boundary nodes receive unbounded (-1) regions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
scipy.spatial.Voronoi: The Voronoi object; |
Source code in src/pyramids/netcdf/ugrid/graph.py
exterior_edges(mesh)
#
Indices of the edges on the mesh boundary.
A boundary edge is one whose edge_face_connectivity has a fill on one side (only one
adjacent face).
A dangling edge -- one the file lists that no face implies -- has a fill on both sides, so it is reported here too. That is deliberate: such an edge bounds no face, which makes it part of the mesh's boundary in the only sense this function can measure. A placeholder row (a padded edge the file declared but never defined, or a repeat of an edge already listed) also has two fills, and that one is not reported, because it describes no edge at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: Boundary edge indices (into the mesh's edge ordering). |
Source code in src/pyramids/netcdf/ugrid/graph.py
exterior_faces(mesh)
#
Indices of the faces that touch the mesh boundary.
A boundary face is one attached to at least one exterior edge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: Sorted unique boundary face indices. |
Source code in src/pyramids/netcdf/ugrid/graph.py
bounding_polygon(mesh)
#
Dissolved outline of the mesh as a shapely (Multi)Polygon, holes preserved.
Unions the face polygons so shared interior edges dissolve away; the result's exterior ring is the mesh outline and any mesh hole appears as an interior ring. Its area equals the mesh footprint. The returned geometry carries no CRS — the caller attaches one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
shapely.Polygon | shapely.MultiPolygon: The dissolved outline. |
Source code in src/pyramids/netcdf/ugrid/graph.py
value_clusters(mesh, values, lower, upper, nodata=None)
#
Label connected groups of faces whose values fall in [lower, upper] (issue #1379).
The value-aware sibling of :func:connected_components, and the mesh analogue of the
raster cluster ("group all the connected values between two bounds").
:func:connected_components labels by topology alone — every face joins a component
whatever it holds — so it cannot answer "which pools of flooded cells are there". This
restricts the face adjacency to the faces inside the range first, then labels what remains.
Faces outside the range (and no-data faces, which cannot be "in" a range) are excluded from
every cluster and labelled 0, so a label is truthy exactly when the face belongs to a
cluster. Clusters themselves are numbered 1..k.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
values
|
ndarray
|
Per-face values, length |
required |
lower
|
float
|
Inclusive lower bound. |
required |
upper
|
float
|
Inclusive upper bound. |
required |
nodata
|
Any
|
Sentinel to exclude, or |
None
|
Returns:
| Type | Description |
|---|---|
NDArray
|
np.ndarray: Per-face label of length |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in src/pyramids/netcdf/ugrid/graph.py
valid_footprint(mesh, values, nodata=None)
#
The dissolved outline of the faces carrying valid values (issue #1379).
The mesh analogue of the raster footprint ("extract the real coverage of the values").
Distinct from :func:bounding_polygon, which dissolves every face and so describes the
mesh domain rather than where data exists; and from polygonize, which dissolves faces of
equal value into one polygon per value.
"Valid" means not nodata and not NaN. It deliberately does not consult a variable's
active (wet/dry) mask: a dry element has a real value and is still covered by the data, so
excluding it would conflate "no data here" with "dry here" — the distinction issue #1331
exists to preserve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh to read. |
required |
values
|
ndarray
|
Per-face values, length |
required |
nodata
|
Any
|
Sentinel to treat as absent, or |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
shapely.Polygon | shapely.MultiPolygon: The dissolved coverage, or an empty polygon when |
Any
|
no face carries a valid value. Carries no CRS — the caller attaches one. |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in src/pyramids/netcdf/ugrid/graph.py
proximity(mesh, target_faces)
#
Graph distance from every face to the nearest target face.
Uses BFS over the face adjacency graph. A target face has distance
zero; an unreachable face (disconnected component with no target)
gets -1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh topology. |
required |
target_faces
|
ndarray
|
1-D array of face indices that are targets. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: Per-face integer distance of length |
Examples:
- Compute hop distance from face 0 in a two-triangle mesh:
>>> import numpy as np >>> from pyramids.netcdf.ugrid.connectivity import Connectivity >>> from pyramids.netcdf.ugrid.mesh import Mesh2d >>> from pyramids.netcdf.ugrid.graph import proximity >>> fnc = Connectivity( ... np.array([[0, 1, 2], [1, 3, 2]]), ... -1, "face_node_connectivity", 0, ... ) >>> mesh = Mesh2d( ... node_x=np.array([0.0, 1.0, 0.5, 1.5]), ... node_y=np.array([0.0, 0.0, 1.0, 1.0]), ... face_node_connectivity=fnc, ... ) >>> proximity(mesh, np.array([0])) array([0, 1]) - All faces as targets gives distance zero everywhere:
>>> import numpy as np >>> from pyramids.netcdf.ugrid.connectivity import Connectivity >>> from pyramids.netcdf.ugrid.mesh import Mesh2d >>> from pyramids.netcdf.ugrid.graph import proximity >>> fnc = Connectivity( ... np.array([[0, 1, 2], [1, 3, 2]]), ... -1, "face_node_connectivity", 0, ... ) >>> mesh = Mesh2d( ... node_x=np.array([0.0, 1.0, 0.5, 1.5]), ... node_y=np.array([0.0, 0.0, 1.0, 1.0]), ... face_node_connectivity=fnc, ... ) >>> proximity(mesh, np.array([0, 1])) array([0, 0])
See Also
sieve: Remove small connected regions using the same adjacency graph. connected_components: Label each connected component.
Source code in src/pyramids/netcdf/ugrid/graph.py
975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 | |
sieve(mesh, values, threshold)
#
Remove small connected regions below a size threshold.
Faces in a connected component smaller than threshold are set to NaN. The result is always float64 regardless of the input dtype.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mesh
|
Mesh2d
|
The mesh topology. |
required |
values
|
ndarray
|
1-D variable data (one value per face). |
required |
threshold
|
int
|
Minimum component size to keep. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: A copy of values with small-region faces blanked. |
Examples:
- Blank faces in a two-face mesh (one component of size 2)
when the threshold exceeds the component size:
>>> import numpy as np >>> from pyramids.netcdf.ugrid.connectivity import Connectivity >>> from pyramids.netcdf.ugrid.mesh import Mesh2d >>> from pyramids.netcdf.ugrid.graph import sieve >>> fnc = Connectivity( ... np.array([[0, 1, 2], [1, 3, 2]]), ... -1, "face_node_connectivity", 0, ... ) >>> mesh = Mesh2d( ... node_x=np.array([0.0, 1.0, 0.5, 1.5]), ... node_y=np.array([0.0, 0.0, 1.0, 1.0]), ... face_node_connectivity=fnc, ... ) >>> sieve(mesh, np.array([5.0, 7.0]), threshold=3) array([nan, nan]) - Keep both faces when the threshold is small enough:
>>> import numpy as np >>> from pyramids.netcdf.ugrid.connectivity import Connectivity >>> from pyramids.netcdf.ugrid.mesh import Mesh2d >>> from pyramids.netcdf.ugrid.graph import sieve >>> fnc = Connectivity( ... np.array([[0, 1, 2], [1, 3, 2]]), ... -1, "face_node_connectivity", 0, ... ) >>> mesh = Mesh2d( ... node_x=np.array([0.0, 1.0, 0.5, 1.5]), ... node_y=np.array([0.0, 0.0, 1.0, 1.0]), ... face_node_connectivity=fnc, ... ) >>> sieve(mesh, np.array([5.0, 7.0]), threshold=1) array([5., 7.])
See Also
proximity: Compute graph distances from target faces. connected_components: The underlying labelling used to identify regions.
Source code in src/pyramids/netcdf/ugrid/graph.py
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 | |