Tessellation to graph#
Overview#
Conversion of a NEPER .tess tessellation into a graph whose nodes are grains and whose
edges are shared faces, as a torch_geometric Data object. The stage is driven by
NeperTessToGraphNN. It exists so that a reconstructed microstructure can
be fed to a graph neural network – predicting a per-grain quantity from the grain’s own
attributes and its neighbourhood, which is the natural data structure for a problem where
what happens in a grain depends on who it is next to.
This stage requires the gnn extra (pip install "graintrace[gnn]").
Method#
A tessellation is already a graph. NEPER’s .tess file stores the full cell complex –
vertices, edges, faces, and which faces bound which cells – so the grain adjacency structure
needs no geometric inference: two grains are neighbours exactly when they share a face.
The construction inverts the cell-to-face lists into a face-to-cell map and keeps the faces with two owners:
Faces with a single owner lie on the domain boundary and contribute no edge: there is no “exterior” node. Grains on the surface of the domain therefore have systematically lower degree than interior grains, which is a real feature of the data – a surface grain genuinely has fewer neighbours inside the reconstruction – but it also means degree is confounded with boundary membership. A model that learns from degree will partly be learning “am I on the edge of the sample”.
A face owned by zero cells, or by more than two, means the tessellation is not a valid
manifold complex. validate_topology counts all four cases and raises on the bad ones
before any graph is built, rather than letting a malformed .tess produce a quietly wrong
adjacency.
Warning
edge_index is built with one directed edge per internal face, source in row 0 and
target in row 1. It is not symmetrised. Most torch_geometric message-passing layers
propagate only along the given direction, so used as-is each pair of neighbouring grains
exchanges information one way only, chosen by whichever cell NEPER happened to list first.
Symmetrise before training unless you specifically want a directed graph:
from torch_geometric.utils import to_undirected
graph = parser.build_cell_graph()
graph.edge_index = to_undirected(graph.edge_index)
Note that symmetrising after the fact invalidates edge_attr, which is indexed
edge-for-edge against the original edge_index. Duplicate the edge features to match if
you have registered any.
Features#
Node and edge features are supplied by registries: a name mapped to a callable, with the
active subset concatenated column-wise at build time. graph.feature_slices records the
column range each feature occupies, so a block can be recovered from the assembled matrix.
Two defaults are worth knowing before use:
The only pre-registered node feature is
seed_centroid, and it is a documented placeholder. It returns the raw.tess*seedrows, which are four columns – \((x, y, z, w)\), the seed position and its Laguerre weight – not a three-column centroid. A seed is also not a cell centroid in general (see FF Voronoi/CVT tessellation).No edge features are registered by default, so
edge_attrcomes back with shape \((E, 0)\). The graph carries topology, not geometry.
register_dataframe_features is the practical route to real node features: it attaches
columns of a per-grain dataframe (orientation, size, strain, anything from the reconstruction)
as named node features, row-aligned with the cell ordering.
Note
The geometry hooks – load_geometry_information and
run_neper_for_geometry_information – are unimplemented placeholders. The
cell_centroid, cell_vol, face_centroid and face_area attributes they would
fill stay empty lists, so face area is not available as an edge weight out of the box.
Compute the geometry separately (neper -T -statcell/-statface) and attach it
through the feature registries.
Orientations#
NEPER writes orientations in whatever descriptor the tessellation was generated with, named
on the first line of the *ori block. The parser reads that descriptor and converts to
canonical neml2 MRP, \(\tan(\theta/4)\,\hat{\mathbf{e}}\), so the graph’s orientations
match every other orientation in the package regardless of how the .tess was produced.
See Notation and conventions for the representations and the conversion rules.
Algorithm#
Parse the
.tesssections: cell seeds and orientations, vertices, edges, face vertices and face edges, and the cell-to-face lists. IDs are 1-based in the file and 0-based in memory; signed face and edge references are preserved.Convert the
*orirows from NEPER’s descriptor to neml2 MRP.Register the default node feature and activate all registered features.
Invert cell-to-face into face-to-cell, validate that every face has one or two owners, and emit one edge per two-owner face as Eq. 12.
Concatenate the active node features into
xand the active edge features intoedge_attr, recordingfeature_names,feature_slicesandedge_feature_nameson the returnedData.
Parameters that matter#
tess_path: the NEPER.tessfile; the only required input.device/dtype: where the tensors are built. The default iscpuandtorch.float64; most GNN stacks expectfloat32, so cast before training.geometry_cell_file/geometry_face_file: intended inputs for the geometry hooks; currently inert (see the note above).
Further details#
For the .tess file format, see the NEPER documentation: https://neper.info/doc/ . For the
Data object, message passing, and the graph utilities referenced here, see
https://pytorch-geometric.readthedocs.io/ .
See also#
FF Voronoi/CVT tessellation – where the
.tesscomes fromNotation and conventions – orientation representations
API:
NeperTessToGraphNN