Repository navigation
Mixed cells plan
Issue #307, 0.8. Status: in review,
#389 (the six phases, clip's docstring, and the
changes from Jeff Lee's review), with the test fixes it depends on in
#388. The point gradients at bad corners moved to a
pull request of their own (vis/point-gradients-bad-corners), since they change every mesh's
point gradients. The performance work that followed is on two branches of berkgeveci/tack-fork,
stacked on #389 and opened after it merges: perf/vtk-reading (reading VTK grids, on the CPU and
a discrete GPU) and perf/analysis (the analysis filters, and the AhmedML sweep benchmark).
The AhmedML times on this page were measured with those two branches applied; #389 alone
reads and analyses more slowly.
One UnstructuredTopology holds shaped cells and polyhedra together, and, in
2D, shaped cells and polygons. It has one point numbering and one cell
numbering. Each face is stored once, with its two sides, whatever kinds of
cells it lies between. Filters return one dataset.
OpenFOAM's meshes are mostly hexahedra and wedges, with polyhedra where the
mesher refines or snaps. AhmedML's volume_1_appended.vtu has 20,897,282
hexahedra, 66,606 wedges and 508,298 polyhedra. Today Tack reads such a mesh
one of two ways:
-
Every cell as a polyhedron (
vtk_to_dataset's default when a polyhedron is present). This takes 27–36 s and about 19.7 GB to read, and every algorithm then runs its polyhedral path on 21M cells, of which 97% are hexahedra. -
Split by type (
polyhedral="split"), into aPartitionedDataSetof a shaped piece and a polyhedral piece. Interface points then exist twice, the pieces' surfaces differ where a hexahedron and a polyhedron share a non-planar face (#337: a sliver no piece holds), and every filter, probe and advection works over pieces.
Mixed cells replace both readings. The shaped cells keep their fast paths, and only the polyhedra pay for being polyhedra.
td.UnstructuredTopology(
types, offsets, connectivity, # as now; types may hold 42 (polyhedron), 7 (polygon)
num_points=None, id_dtype=None,
polyhedra=None, # the polyhedra's faces, when there are polyhedra:
# (face_offsets, face_points,
# cell_face_offsets, cell_faces, cell_face_sides)
)-
types,offsetsandconnectivityare VTK's. A polyhedron's connectivity is its distinct points and a polygon's is its loop, so the three arrays stay shareable with VTK (copy=False) and with warp-simdata. -
The polyhedra's faces are stored the way
PolyhedralTopologystores them, with each face once:-
face_offsets/face_pointsgive each face's points, in the order that winds the face out of its side 0; -
cell_faces/cell_face_sidesgive each polyhedron's faces and which side of each it is on; -
cell_face_offsetshas one entry per cell plus one, and is empty for a shaped cell, as VTK's face locations are.
A polyhedral view then needs no change: its
cell(c)is the global cell id. On AhmedML the per-cell offsets cost 86 MB in i32, against the 19.7 GB of today's all-polyhedra reading. -
-
Polygons need no extra arrays. A polygon's connectivity is its loop, so its edges come from consecutive points, as a
PolygonalTopology's do. -
Every cell is either a shape or a polyhedral cell of its own group.
groups()gives one group per shape present, as now, plus aPolyhedrongroup and aPolygongroup of the cells of type 42 and 7. The latter are selected by id (_SelectedPolyhedra; a polygon view of the same kind). -
Checks.
validate()and debug mode also check the face arrays (CSR ranges, sides 0/1, at most two sides a face), that only polyhedra have faces, and that a polyhedron's connectivity is its faces' points. -
PolyhedralTopology,PolygonalTopologyandPolylineTopologystay as they are. Folding the first two in as the all-polyhedra case is decided once the mixed path works, and goes through the deprecation mechanism below if it is done.
faces() gives one record for every face of the cells of the highest
dimension:
-
Triangles and quadrilaterals stay in fixed rows, the record shapes
have today (
rows,kinds,sides, the per-(cell, local face) arrays). A polyhedron's triangular or quadrilateral face goes into the same match as the shaped cells' faces, by its point set. So a face between a hexahedron and a polyhedron is one face with two sides, andlocalon the polyhedron's side is its index incell_faces. -
Faces of five or more points can only be polyhedral. They go in a CSR
group of their own (
_PolygonFaces), matched asPolygonFacesmatches them now. - A mesh without polyhedra derives its faces exactly as now.
-
The per-(cell, local face) arrays (
side_face,side_slot,side_orientation) keep their fixed stride for shape groups. For the polyhedral group they are CSR overcell_faces, asPolygonFaceshas them;group_startssays which layout a group has. - T-junctions (#319), where one polyhedral face covers several shaped faces, are designed with this record. A shaped face with no partner keeps one side, and a later step matches it to the larger face. Building that is not part of this plan.
-
edges()andpoint_links()join the shaped groups' corner layout with the polyhedral group's cell-point entries. The polyhedral group's cell points are its connectivity, not derived.
Every caller that tests which record it has (hasattr(faces, "rows"),
extract_surface, generate_global_ids, connected_components, sets)
reads the groups instead.
About 40 places decide "polyhedral or not" for the whole topology:
reference_cells, by_id.NO_REFERENCE and CellLocator.polyhedral. Each
becomes a property of a group's shape (shape.REFERENCE, which Polyhedron,
Polygon and PolyLine set false). A topology-wide reference_cells
stays as "every group has one", for the spaces that need a reference element
(H1 above order 1, L2), which refuse a topology with polyhedra as now.
| Phase | What | Modules |
|---|---|---|
| 1 | The topology, faces(), edges(), point_links(), groups, checks; vtk_to_dataset and dataset_to_vtk; keeping a subset of cells |
topology.py, polyhedra.py, views.py, dataset.py, _extract._keep_cells (so extract_cells, threshold's output and remove_ghosts), interop/vtk
|
| 2 | Per-cell work per group | gradients, integrate, cell_geometry, cell_centers, values_at_centers, project, cell_quality, surface_normals, threshold's flags, feature_edges, jump, traces, spaces |
| 3 | Filters that build a new topology, their shaped and polyhedral parts joined into one | contour and slice (triangles and polygons), clip (shapes and polyhedra), extract_surface, merge_points, triangulate, tetrahedralize, as_polyhedra, as_polygons, check_winding, orient
|
| 4 | Cells by id: the choice per cell type inside the kernel |
by_id, locator, probe, voxelize, advection and streamlines |
| 5 | Pieces | ghost_cells, merge_pieces, generate_global_ids, connected_components; polyhedral="split" deprecated (#337) |
| 6 | The whole | every call of test_dataset_devices.CALLS on a mixed mesh; AhmedML measured |
Done so far. Phase 1: the topology, faces(), edges(), groups, checks,
VTK both ways, keeping a subset of cells; AhmedML reads in 10 s (40-54 s as
polyhedra). Phase 2: every algorithm in its row, except surface_normals and
feature_edges, which take surfaces (phase 3's 2D mixed meshes), and point_links,
moved here from phase 1. traces refuses polyhedra with faces of more than four
points, which SideTraces cannot hold; point data on a mixed mesh is continuous,
so jump and upwind_flux take it without traces. On AhmedML, warm: cell
gradients 0.1 s, point gradients 1.1 s, cell_quality 1.1 s, integrate 0.9 s.
Phase 3: polygons beside triangles and quads (type 7, edges derived from
the loops), and every filter in its row except orient, which the VTK reader
already applies. clip keeps whole shapes and makes every other kept cell the
polyhedron of its kept part, since a cut shape's table pieces would not match
a neighbouring polyhedron's clipped faces; tetrahedralize adds a point per
polyhedron and per face of five or more points. Not yet: shrink, subdivide
and clip of polygons refuse. (vtk_to_dataset read polygons as a
PolygonalTopology until the review of #389; see below.) On AhmedML, warm, mixed against all polyhedra:
surface 0.1 s against 0.0, slice 0.1 against 0.1, contour 0.2 against 0.3,
clip 0.6 against 0.4, with the same points and measures; peak memory 22 GB
against 28.
Phase 4: the locator, probe, voxelize, advect and streamlines. Each cell
is located by its type, a shape by its coordinates and a polyhedron or polygon
in its fan; the per-cell test is compiled in only for mixed meshes, and the
optimized code of the search and advection kernels on shaped and polyhedral
meshes is unchanged. The walk to face neighbours (advect(..., neighbors=True))
stays off with polyhedra. On AhmedML (240K probes, 200 streamlines), mixed
against all polyhedra: locator 0.2 s against 1.3, first probe 0.5 against 0.7,
first streamlines 0.6 against 2.8, both warm under 0.05 s; probes in polyhedra
equal to rounding, in hexahedra trilinear (up to 2% of the velocity apart);
peak memory 17 GB against 25.
Phase 5: ghost_cells, update_ghosts, merge_pieces, generate_global_ids
and connected_components over pieces, in one process and across MPI ranks. A
polyhedron travels with its faces wound out of it and is matched again where it
lands, so a piece without polyhedra of its own takes them as ghosts, and a merge
gives the mixed mesh back. The deprecation mechanism is in as proposed below,
except that deprecated(...) declares a deprecation (in a declaration module per
package, so the registry needs no VTK) and the deprecated code calls its
warn(); polyhedral="split" is deprecated in 0.8, to be removed in 0.9. Still
refused on mixed meshes: shrink, subdivide, and clip of surfaces with
polygons.
Phase 6: the mixed-mesh refusal is gone. shrink and subdivide refuse a mixed
mesh as they refuse polyhedra, for want of a reference element, as H1 above
order 1 and L2 do; the sweeps hold every refusal to the all-polyhedra mesh's
(in 2D, the all-polygons surface's), and the device sweeps run every call on a
mixed mesh and on quads beside polygons, on a second device and on one without
f64. On AhmedML a pipeline of gradients, quality, integration, surface, slice,
contour, clip, probe and streamlines takes 3.7 s warm mixed against 4.2 s as all
polyhedra, after reading in 8.7 s against 32 s, at a peak of 25 GB against 33;
the answers agree. The mixed mesh costs what its parts cost alone.
Reading polygons (review of #389, d8e8b85e): vtk_to_dataset reads a grid
or vtkPolyData with polygons as an UnstructuredTopology, the polygons
beside the triangles and quads, so the reader breaks once, in 0.8, for
polyhedra and polygons alike; polyhedral="all" gives the old
PolygonalTopology. Whether PolygonalTopology and PolyhedralTopology are
still needed is question 4 below, measured together.
Left open: shrink and subdivide on polyhedra, and clip of polygons, are polyhedra and
polygon work, not mixed meshes'; advection's walk to face neighbours is off with
polyhedra.
Point gradients at bad corners (done after phase 6). AhmedML's tangled and nearly flat hexahedra gave point gradients up to 2e9, on the hexahedra alone too. A cell folded over (a corner's scaled Jacobian below -1e-3) now gives every corner its Green-Gauss gradient over its faces, as a polyhedron's; a corner flat but for rounding (within 1e-3 of 0, f32 rounding of AhmedML's small cells having reached 2e-4) is left out of the point's average, as an exactly flat one was. The largest is now 1.7e3 (a valid corner of scaled Jacobian 0.008), against 132 read as all polyhedra, with a median difference of 1e-4.
Each phase is a pull request. After phase 1 a mixed mesh can be read, written, checked and subset, and every algorithm not yet ported refuses it by name, never silently computing on part of the cells.
Phase 3's joined outputs. Contour and slice produce triangles from the shapes and polygons from the polyhedra, so their output is a 2D mixed topology: triangles beside polygons. Clip produces shapes and polyhedra in one topology. That also opens the way for clip through mesh points (#309), whose 132 table patterns fit only as polyhedra.
Phase 4's cell lookup. Today the mesh class is chosen once for the whole
topology (_FlatCells, _PolyhedralMesh, ...). A mixed mesh's kernel reads
the cell's type and takes the fan path for a polyhedron. This must not slow
the shaped meshes: the check is compiled in only when the topology has
polyhedra, and benchmarks/polyhedra_compare.py and the advection benchmarks
hold the shaped cost.
-
vtk_to_datasetof an unstructured grid with polyhedra gives a mixed topology:-
types,offsetsandconnectivityare shared with VTK where the ids allow; - VTK's per-cell face copies are matched once, among the polyhedra only
(
_match_rings), into the face arrays; -
orient=Trueandmerge_points=work as they do today, since AhmedML needs both (198,919 faces wound the same way by both cells, and 1.56M duplicate points).
-
-
The old readings stay available:
polyhedral="all"reads every cell as a polyhedron, as today's default does.polyhedral="split"is deprecated and removed after a release. -
dataset_to_vtkwritesSetPolyhedralCellswith each face copied per cell, reversed for side 1, as for aPolyhedralTopologynow. - warp-simdata comes in phase 3 or later, as its users ask.
The default reading of a file with polyhedra changes from a
PolyhedralTopology to a mixed UnstructuredTopology. That is a breaking
change in the CHANGELOG. polyhedral="all" gives the old result.
Tack has no way to deprecate something yet; it has only removed things
outright. This work needs one for polyhedral="split", and later perhaps for
PolyhedralTopology. Proposed:
-
tack._deprecation(tack-core) withdeprecated(what, *, since, removed_in, use). It warns once per call site withTackDeprecationWarning, a subclass ofDeprecationWarning(as NumPy's are), at the caller's line. -
A test that fails once a deprecation is due. Every use is recorded in
a registry;
test_deprecations.pyfails onceremoved_inis at or before the package's version, so a release cannot ship a deprecation past its date. -
Documented. The CHANGELOG gains a
### Deprecatedsection (tools/changelog.pyaccepts it), and the docs list current deprecations on one page generated from the registry. - Policy. Deprecate in one minor release and remove in the next or later. Before 1.0, a breaking change may still go without deprecation where keeping the old behaviour would cost more than it saves; the CHANGELOG says so.
-
A mixed mesh against the same mesh read two other ways: as all
polyhedra (
as_polyhedraof its shaped cells,polyhedral="all") and split by type. Per cell and per point, values agree to rounding with the all-polyhedra reading wherever the cell is a polyhedron in both, and with the shaped reading wherever it is a shape in both. - VTK for what it computes on mixed grids: cell sizes, contours, clips and slices, surfaces.
-
Small meshes built for the purpose:
- a hexahedron next to a polyhedron sharing a quad face;
- a wedge next to a polyhedron sharing a triangle;
- two polyhedra sharing a pentagon;
- a polyhedron whose face covers two hexahedra' faces (a T-junction: refused or one-sided until #319);
- a 2D mesh of triangles, quads and polygons.
-
AhmedML for scale: the memory and time of reading,
extract_surface, slices and streamlines, against both of today's readings. -
Every device, through
test_dataset_devices' fixtures, as for any dataset.
-
Must a polyhedron's connectivity be sorted?
PolyhedralTopology's derived cell points are sorted and unique. VTK's are only distinct. Taking VTK's as they are keeps them shareable. Does anything rely on the order? (merge_points, ghost matching and keys built from a cell's points need checking.) Answer: no. Per-cell loops take every point (centres, boxes, marks, thresholds); corner keys and point links take each corner's own point; faces are matched by their own points, sorted there (_face_keysinfaces(),_match_ringsfor ghosts,merge_pointsand the VTK reader), never by a cell's; the locator's weights are made and read in the same order. The mixed-cells tests build their polyhedra in hexahedron-corner order, which is not sorted, and hold them to the sorted all-polyhedra reading. The views' docstring says so (#389's review). -
Size buckets (
SizeBuckets) group polyhedra by their face-point counts. Do they work over a selected group unchanged, or do they need the group's ids? Answer: unchanged.cell_keyskeys every cell of the topology by its global id (a mixed topology'scell_face_offsetsare per cell, a shape's range empty, so the smallest bucket), andlaunch_groupssplits each group, selected ones included, by its cells' keys. Contour's mixed path (_lopez_polygons(only=)) runs this way. - Non-manifold faces (#308): still refused, as now: any face of more than two cells, whatever the cells are. A shaped cell's face on a face two polyhedra share was left a face of its own until #389's review; it is refused too.
-
Folding
PolyhedralTopologyin, after phase 6: is it worth one class fewer, given the deprecation it would take?