Skip to content

Commit 9d71198

Browse files
committed
Speed up UGrid polygon generation
1 parent 41f9fd6 commit 9d71198

File tree

2 files changed

+21
-12
lines changed

2 files changed

+21
-12
lines changed

src/emsarray/conventions/ugrid.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import pathlib
1111
import warnings
1212
from collections import defaultdict
13-
from collections.abc import Hashable, Iterable, Mapping, Sequence
13+
from collections.abc import Hashable, Iterable, Sequence
1414
from contextlib import suppress
1515
from dataclasses import dataclass
1616
from functools import cached_property
@@ -514,7 +514,8 @@ def _to_index_array(
514514
else:
515515
# If the value is still an integer then it had no fill value.
516516
# Convert it to a mask array with no masked values.
517-
values = numpy.ma.masked_array(values, mask=numpy.ma.nomask)
517+
mask = numpy.full_like(values, fill_value=numpy.ma.nomask)
518+
values = numpy.ma.masked_array(values, mask=mask)
518519

519520
# UGRID conventions allow for zero based or one based indexing.
520521
# To be consistent we convert all indexes to zero based.
@@ -1078,24 +1079,31 @@ def grid_kinds(self) -> frozenset[UGridKind]:
10781079

10791080
def _make_polygons(self) -> numpy.ndarray:
10801081
"""Generate list of Polygons"""
1081-
# X,Y coords of each node
10821082
topology = self.topology
1083+
# X,Y coords of each node
10831084
node_x = topology.node_x.values
10841085
node_y = topology.node_y.values
1086+
# The nodes that each face is constructed from
10851087
face_node = topology.face_node_array
1088+
1089+
# Preallocate an array to put the polygons in
10861090
polygons = numpy.full(topology.face_count, None, dtype=numpy.object_)
10871091

10881092
# `shapely.polygons` will make polygons with the same number of vertices.
10891093
# UGRID polygons have arbitrary numbers of vertices.
10901094
# Group polygons by how many vertices they have, then make them in bulk.
1091-
polygons_of_size: Mapping[int, dict[int, numpy.ndarray]] = defaultdict(dict)
1092-
for index, row in enumerate(face_node):
1093-
vertices = row.compressed()
1094-
polygons_of_size[vertices.size][index] = numpy.c_[node_x[vertices], node_y[vertices]]
1095-
1096-
for size, size_polygons in polygons_of_size.items():
1097-
coords = numpy.stack(list(size_polygons.values()))
1098-
shapely.polygons(coords, indices=list(size_polygons.keys()), out=polygons)
1095+
# Polygon sizes can be derived by how many nodes are masked/not masked.
1096+
polygon_sizes = numpy.sum(~numpy.ma.getmaskarray(face_node), axis=1)
1097+
unique_sizes = numpy.unique(polygon_sizes)
1098+
1099+
# Make polygons in batches
1100+
for unique_size in unique_sizes:
1101+
# Extract the face node data for every polygon of this size
1102+
indices = numpy.flatnonzero(polygon_sizes == unique_size)
1103+
nodes = numpy.ma.getdata(face_node)[indices, :unique_size]
1104+
coords = numpy.stack([node_x[nodes], node_y[nodes]], axis=-1)
1105+
# Generate the polygons directly in to their correct locations
1106+
shapely.polygons(coords, indices=indices, out=polygons)
10991107

11001108
return polygons
11011109

tests/conventions/test_ugrid.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def make_faces(width: int, height, fill_value: int) -> tuple[numpy.ndarray, nump
4040

4141
face_node: numpy.ndarray = numpy.ma.masked_array(
4242
numpy.full((total_faces, 4), fill_value, dtype=numpy.int32),
43-
mask=True, fill_value=fill_value)
43+
mask=numpy.full((total_faces, 4), True),
44+
fill_value=fill_value)
4445
edge_node = numpy.zeros((total_edges, 2), dtype=numpy.int32)
4546

4647
for row in range(1, width + 1):

0 commit comments

Comments
 (0)