graintrace.fragmentation#
Grain fragmentation and reorientation analysis.
FragmentationAnalyzer groups the reorientation and fragmentation
capabilities that share the same crystal symmetry and column conventions:
reorientation (misorientation-from-a-reference-step) as a rare-event scalar;
orientation-based point-set segmentation (graph/Leiden) and its reduction to a per-grain table, shared by the simulation and experiment paths;
fragmentation detection: simulation intragranular sub-grains (
FragmentationAnalyzer.grain_fragments()) and experiment grain splits across load steps (FragmentationAnalyzer.detect_splits()).
The segmenter uses a fixed broad RBF sigma (sigma_auto collapses on
near-identical intra-grain edges and shatters grains), a hard misorientation edge
cutoff, gamma chosen by percolation, then fragment absorption and an
adjacency-only re-merge.
Heavy dependencies (torch, neml2, scipy, networkit via GraphSpatialCluster)
are imported lazily inside the methods.
- class graintrace.fragmentation.FragmentationAnalyzer(symmetry='432', ori_cols=('ori_rodrigues_x', 'ori_rodrigues_y', 'ori_rodrigues_z'), coord_cols=('x', 'y', 'z'))[source]#
Bases:
objectReorientation and fragmentation analysis for simulation and experiment data.
Holds the crystal symmetry and the simulation orientation/coordinate column conventions shared by the methods. The experiment split detector (
detect_splits()) takes its own column params because grain tables use a different schema (Eul0/1/2,X/Y/Z).- Parameters:
symmetry (str) – crystal symmetry in orbifold notation (default
"432").ori_cols (Sequence[str]) – per-element neml2 MRP orientation columns in the simulation field CSVs (default
ori_rodrigues_x/y/z).coord_cols (Sequence[str]) – per-element coordinate columns in the simulation field CSVs (default
x/y/z).
- reorientation_from_reference(results, step, ref_step=None)[source]#
Per-element misorientation angle (deg) from a reference load step.
Joins the per-element field CSVs at
ref_stepandsteponidand returns the symmetry-reduced misorientation between the two orientations at each shared element. Theori_colshold neml2 v3 MRP orientations.- Parameters:
results – a
SimulationResultswhosefield_dirholds per-element CSVs.step (int) – field-step index to measure.
ref_step (int | None) – reference field-step index (defaults to the first field step).
- Returns:
element ids
(N,)and misorientation angles in degrees(N,), aligned to the ids common to both steps.- Return type:
(ids, deg)
- write_reorientation_field(results, step, out_csv, ref_step=None, extra_cols=())[source]#
Write
id,x,y,z,reorientation_deg(+ optional passthrough) for REI.The output is consumed unchanged by
IdentifyRareClusters. Passingextra_cols(e.g.nye_tensor_*or stress columns) carries those features through so the same CSV can drive a combined rare-event criterion.- Parameters:
- Returns:
The output CSV path.
- Return type:
- static mean_orientation_mrp(mrp_arr, symmetry='1')[source]#
Symmetry-aware quaternion (eigenvector) mean of neml2 MRP orientations.
A proper orientation mean (NOT an arithmetic mean of the MRP components). When
symmetrynames a crystal point group, every orientation is first folded to the symmetry-equivalent variant closest to the first (reference) orientation, so a set whose members are reported in different symmetry variants (which the plain quaternion mean would average into a meaningless result) still yields the correct mean. Withsymmetry="1"(the default) this reduces to the plain quaternion eigenvector (Markley) mean.
- static merge_fragments_by_misorientation(mrp, labels, tol_deg, symmetry='432')[source]#
Merge sub-grain fragments whose mean orientations are within
tol_deg.Agglomerative, orientation-only merge for intragranular sub-grains: the two fragments with the smallest (symmetry-aware) mean misorientation are merged first, the merged mean is recomputed, and the process repeats until the closest remaining pair is at least
tol_degapart. Recomputing the mean each step avoids the single-linkage chaining that a fixed-mean union-find would suffer. Use it to impose a minimum sub-grain misorientation so a smooth intragranular gradient is not reported as several near-identical fragments.- Parameters:
- Returns:
(N,)contiguous 0-based labels after merging.- Return type:
- static absorb_fragments(coords, labels, min_size, k_neighbors=8, max_passes=25)[source]#
Absorb sub-
min_sizeclusters into their largest-contact neighbour.Uses kNN contact counts for adjacency, so it works for any point set (voxel grids or scattered mesh centroids). Iterates until no sub-
min_sizecluster remains (or no further merge is possible), then relabels contiguously from 0.- Parameters:
- Returns:
(N,)contiguous 0-based labels.- Return type:
- static adjacency_remerge(coords, mrp, labels, tol_deg, symmetry='432', k_neighbors=8, percolation_max_frac=0.03)[source]#
Merge spatially adjacent clusters within
tol_degmisorientation.Tightest (lowest-misorientation) adjacent pairs merge first, and any union that would exceed
percolation_max_fracof the point count is skipped so a chain of low-angle fragments cannot re-percolate into one giant grain (the failure mode a global orientation merge or naive single-linkage produces). Adjacency is kNN contact, so it works for both voxel grids and scattered mesh element centroids.Intended for large, many-cluster segmentations (NF/EBSD grids): the size cap is a fraction of the whole point set, so on a small single-grain point set (e.g. one grain’s ~100 elements in
grain_fragments()) it is smaller than any fragment and blocks every merge (inert). Raising the cap there instead lets union-find chain low-angle fragments (the means are computed once), which over-merges. So sub-grain segmentation disables re-merge and relies ongamma.- Parameters:
coords (ndarray) –
(N, 3)point coordinates.mrp (ndarray) –
(N, 3)neml2 MRP orientations.labels (ndarray) –
(N,)integer cluster labels.tol_deg (float) – merge adjacent clusters whose mean orientations are within this.
symmetry (str) – crystal symmetry (orbifold notation).
k_neighbors (int) – neighbours per point used to measure cluster contact.
percolation_max_frac (float) – hard cap on any merged cluster’s size fraction.
- Returns:
(N,)contiguous 0-based labels.- Return type:
- static gamma_sweep_leiden(csv_path, ckpt_base, gammas, n_points, spec, weight_cfg, max_edge_distance, graph_mode='grid', manhattan_radius=2, k=16, reduce_topk=12, n_jobs=1, n_threads=1, seed=42)[source]#
Build the misorientation graph once, then run Leiden per gamma.
The edges/weights depend only on the graph + misorientation cutoff (not gamma), so they are checkpointed and reused across the sweep.
- Returns:
list of dicts with keys
gamma,n_grains,max_frac,labels.- Parameters:
- Return type:
- static pick_gamma_by_percolation(sweep, percolation_max_frac=0.03)[source]#
Pick the smallest gamma whose biggest cluster is below the percolation cap.
The raw Leiden count is near gamma-invariant here (all gammas over-segment); gamma controls only the biggest (“percolating”) cluster, and the final grain count is set by absorption. So choose the least over-resolved, still de-percolated gamma; if none qualifies, the overall minimum
max_frac.
- segment(coords, mrp, misori_tol_deg=5.0, graph_mode='grid', gamma=None, gamma_sweep=(0.5, 1.0, 2.0, 4.0, 8.0), rbf_sigma_deg=None, manhattan_radius=2, k=16, reduce_topk=12, grain_threshold_final=30, remerge_miso_deg=3.0, percolation_max_frac=0.03, n_jobs=1, n_threads=1, seed=42, workdir=None)[source]#
Segment a point set by crystal orientation (graph / Leiden).
Preferred over flood fill, which over-merges into percolating grains. Wraps
GraphSpatialClusterwith the symmetry-aware misorientation metric on neml2 MRP orientations and the tuned recipe: fixed broad RBF sigma, hard misorientation cutoff, gamma-by-percolation, then absorb + adjacency re-merge.- Parameters:
coords (ndarray) –
(N, 3)point coordinates (voxel centres or element centroids).mrp (ndarray) –
(N, 3)neml2 MRP orientations.misori_tol_deg (float) – hard misorientation edge cutoff (degrees).
graph_mode (str) –
"grid"for regular voxel grids (NF/EBSD),"knn"for scattered mesh element centroids.gamma (float | None) – fixed Leiden resolution; if
None, a percolation-picked sweep.gamma_sweep (Sequence[float]) – resolutions to sweep when
gammaisNone.rbf_sigma_deg (float | None) – fixed RBF sigma (degrees); defaults to half
misori_tol_deg.manhattan_radius (int) – grid connectivity radius (
graph_mode="grid").k (int) – neighbours per node (
graph_mode="knn").reduce_topk (int | None) – keep top-k edges per node by weight before clustering.
grain_threshold_final (int) – minimum cluster size; smaller ones are absorbed.
remerge_miso_deg (float) – adjacency re-merge misorientation threshold (degrees).
percolation_max_frac (float) – percolation cap for gamma pick and re-merge.
n_jobs (int) – parallel workers for edge-distance build.
n_threads (int) – threads for Leiden.
seed (int) – RNG seed.
workdir (str | None) – scratch directory for the graph input CSV + checkpoint (a temp directory is used and cleaned up if
None).
- Returns:
(N,)contiguous 0-based cluster labels aligned tocoordsrows.- Return type:
- seg_to_grain_table(labels, coords, mrp=None, euler=None, angle_convention='bunge', angle_type='radians', out_csv=None, background_label=-1)[source]#
Reduce a labelled point set to a per-grain table (FF/NF/EBSD connector).
Produces the
grain_id, X, Y, Z, GrainRadius, Eul0, Eul1, Eul2schema shared by the FF reconstruction and synthetic-load experiment CSVs, so segmented NF/EBSD voxel grids and FF grain tables converge on one grain table thatdetect_splits()consumes.Orientation is a proper quaternion mean per grain (not an arithmetic mean of Euler angles). Provide orientations as either
mrp(neml2 MRP) oreuler(withangle_convention/angle_type).- Parameters:
labels (ndarray) –
(N,)integer grain labels.coords (ndarray) –
(N, 3)point coordinates.mrp (ndarray | None) –
(N, 3)neml2 MRP orientations (mutually exclusive witheuler).euler (ndarray | None) –
(N, 3)Euler angles (mutually exclusive withmrp).angle_convention (str) – Euler convention (used when
euleris given).angle_type (str) –
"degrees"or"radians"for the inputeuler.out_csv (str | None) – if given, also write the table to this CSV path.
background_label (int) – the single label treated as void and skipped.
- Return type:
Warning
background_labeldefaults to-1, but a segmented NF or EBSD grid marks void as0and uses-1for material that was not segmented. Left at the default on such a grid, the whole void region is emitted as one enormous “grain” – with a centroid near the sample centre and a meaningless mean orientation – which then matches against the next step indetect_splits(). Passbackground_label=0for a grid produced by the NF/EBSD path, and mask or absorb any remaining-1voxels first. Only one label can be excluded per call.
- grain_fragments(results, step, tol_deg=5.0, block_id_col='block_id', grain_ids=None, min_fragment_elems=5, min_subgrain_miso_deg=1.0, seg_kwargs=None)[source]#
Re-segment each original grain’s elements by orientation at a loaded step.
Groups the per-element field at
stepbyblock_id(the original grain subdomain), runssegment()(graph_mode="knn"for scattered mesh elements) within each grain, and composes afragment_labelof the form"{grain}.{k}"(k the 0-based sub-grain index). This measures intragranular fragmentation – how a single grain breaks into orientation sub-domains under load – keeping the parent grain id.- Parameters:
results – a
SimulationResults.step (int) – field-step index to analyse.
tol_deg (float) – misorientation tolerance (degrees) for the sub-grain segmenter.
block_id_col (str) – the grain/block-id column in the field CSV.
grain_ids (Sequence[int] | None) – grains (block ids) to analyse; defaults to all blocks present.
min_fragment_elems (int) – grains with fewer elements than this are left whole (one fragment); also passed as the segmenter absorb threshold.
min_subgrain_miso_deg (float) – minimum misorientation (degrees) between two sub-grains for them to count as distinct fragments. After Leiden, fragments whose mean orientations are closer than this are merged (agglomeratively, closest-pair first, means recomputed each step), so a smooth intragranular gradient chopped into near-identical Leiden communities collapses back to one fragment while genuine sub-grains (a real low-angle sub-boundary) survive.
0disables the merge.seg_kwargs (Dict | None) – extra keyword overrides forwarded to
segment().
- Returns:
(fragments_df, per_element_labels)wherefragments_dfhas one row per grain (grain_id,n_fragments,fragment_sizes,n_elements) andper_element_labelsmaps each elementidto its"{grain}.{k}"fragment label.- Return type:
- detect_splits(nodes_a, nodes_b, d_tol, theta_tol_deg, angle_convention='bunge', angle_type='degrees', ori_cols=('Eul0', 'Eul1', 'Eul2'), coord_cols=('X', 'Y', 'Z'), id_col='grain_id', top_k=8, adjacency_b=None, out_csv=None)[source]#
Detect grain splits/merges/births/deaths between two load steps.
Goes beyond the strictly one-to-one grain matcher: candidate cross edges from a centroid
cKDTree(top-k) are kept when the centroid distance is withind_toland the misorientation withintheta_tol_deg; connected components of the step-A/step-B union graph are grain lineages, classified by their(nA, nB)shape. FF supplies the node tables directly; NF/EBSD reach the same detector viasegment()+seg_to_grain_table().The grain tables use their own
ori_cols/coord_cols(Eul0/1/2andX/Y/Zby default), independent of the simulation column conventions on the instance.- Parameters:
nodes_a – per-grain node tables (DataFrame or CSV path) for the two steps, with an id column, three coordinate columns, and three orientation columns.
nodes_b – per-grain node tables (DataFrame or CSV path) for the two steps, with an id column, three coordinate columns, and three orientation columns.
d_tol (float) – maximum centroid distance for a candidate cross edge.
theta_tol_deg (float) – maximum misorientation (degrees) for a candidate cross edge.
angle_convention (str) –
"bunge"/"kocks"/"roe"Euler, or"mrp".angle_type (str) –
"degrees"or"radians"(Euler conventions only).id_col (str) – the grain-id column.
top_k (int) – nearest B centroids probed per A grain. This truncation happens before the
d_tolgate, not after it: the KD-tree returns thetop_knearest B grains and only those are then tested againstd_tolandtheta_tol_deg. Sotop_k, notd_tol, is the binding limit on how many children a split can have whenever more thantop_kB grains sit withind_tol. A parent that fragments into many pieces, or a dense fine-grained step B, needstop_kraised as well asd_tol; raisingd_tolalone will not surface the extra children.adjacency_b (Iterable[Tuple[int, int]] | None) – optional iterable of
(grain_id, grain_id)pairs giving intra-step-B grain adjacency; used to reject a spurious split child that is not adjacent to any other child of the same parent. If no two of a parent’s candidates are mutually adjacent there is no split to reject, so the single best candidate (lowest misorientation, then shortest centroid distance) is kept and the parent matches it rather than being dropped entirely.out_csv (str | None) – if given, write the split correspondences to this CSV path.
- Returns:
dict with
components(list of{a_ids, b_ids, kind}),split_correspondences(DataFrame), and the countsn_splits,n_merges,n_matches,n_births,n_deaths.- Return type: