FF Voronoi/CVT tessellation#
Overview#
Reconstruction of a 3D grain structure from far-field (FF) HEDM grain centroids by driving
NEPER to build a Voronoi (or Laguerre) tessellation, optionally CVT-relaxed. The stage is
driven by VoronoiMeshBuilder, which produces a .tess tessellation,
per-grain orientation and initial elastic-strain files, and a voxelized reconstruction that
feeds meshing and CPFE.
Method#
The problem this stage solves is an inverse one, and that framing explains every option below. FF-HEDM does not measure grain shape. For each grain it reports a volume centroid, an equivalent-sphere radius, an orientation and a lattice strain – four observables, none of which is a grain boundary. CPFE needs a space-filling partition of the domain. A tessellation is the standard way to manufacture one, and the question is only which tessellation, seeded how.
Important
The resulting cell shapes are a plausible reconstruction, not a measurement. A tessellation cell is convex; real grains are not. Cell \(z\)-extents recovered this way are roughly \(r/3\) noisy against ground truth even for the generating seeds, and for strongly elongated grains the per-scan tessellation cannot recover the true extent at all. For grain morphology that is actually measured, use NF-HEDM (Grain segmentation). Use this stage for the orientation and strain fields it carries, and for a geometry that is statistically reasonable.
Voronoi and Laguerre cells#
A standard Voronoi tessellation assigns every point of the domain to the nearest seed,
producing space-filling convex cells \(C_i\). Every cell boundary is the perpendicular bisector of a seed pair, so cell size is controlled only by seed spacing: a Voronoi tessellation of measured centroids cannot make one grain bigger than its neighbours except by being further from them.
A Laguerre (power) tessellation removes that restriction by replacing the Euclidean distance
with the power distance \(\lVert x - s_i \rVert^2 - w_i\), which moves each bisecting
plane toward the lower-weighted seed. With weighted=True graintrace derives the weight
from the measured GrainRadius as the grain volume (3D) or area (2D), normalised to sum to
one:
Only the ratios between weights survive that normalisation; the absolute scale does not.
The weights are passed to NEPER as the morphology optimisation’s starting point
(-morphooptiini weight:), not as a final specification, so the tessellation NEPER returns
is the one its optimiser converges to rather than the exact Laguerre diagram of
Eq. 11.
Seeding modes#
option chooses what the measured centroids are to NEPER – initial seed positions, or an
optimisation target. The distinction matters: a seed and a cell centroid are not the same
point, and FF measures the latter.
|
NEPER morphology |
What the measured data is used as |
|---|---|---|
|
|
initial seed positions ( |
|
|
initial seed positions for a CVT relaxation |
|
|
an optimisation target: NEPER moves seeds until cell centroids match. Seeds are
initialised from NEPER’s own packing ( |
|
|
an optimisation target on centroid and size together; requires |
"centroid" is the documented default for FF reconstruction because it targets the
observable FF actually reports. Seeding at the measured centroids instead ("voronoi")
asserts that the centroid is the seed, which is true only for a grain whose cell happens to be
symmetric about it.
Note
"centroid" and "centroidal" are not synonyms, despite looking like one. They
select different NEPER morphologies and different initialisations, as the table shows.
"centroid" fits centroids to the data; "centroidal" is a CVT relaxation seeded from
the data, which moves cells away from the measured positions.
Note
weighted=True applies only on the "voronoi"/"centroidal" path. The two
centroid* targets ignore it – "centroidsize" carries the radius in its own target
file instead. On the weighted path morphoalgo is also overridden to "lloyd"
regardless of what the caller passed.
NEPER carries out the tessellation and the optimisation; graintrace assembles the seeds,
weights, orientations and bounding box, invokes neper -T, and parses the results.
CVT_iter sets the optimiser’s iteration budget (-morphooptistop iter=) and
morphoalgo its algorithm (-morphooptialgo); iter=0 disables relaxation entirely,
which is what the stitching extent helper uses when it wants cells exactly at the measured
seeds.
Algorithm#
Read the FF grain CSV; parse centroids, Euler orientations, and the 9-component elastic strain tensor per grain.
Optionally apply a sample-tilt rotation (
rotate_angles) or PCA alignment (auto_rotate) to the point cloud and orientations.Reconcile the data against
bounding_box(auto_fix_bboxwithremove_pointsorextend_bounding_box).Write the NEPER seed/weight inputs and invoke NEPER (
neper -T) with the chosenoptionand CVT settings (CVT_iter,morphoalgo).Collect outputs:
reconstruction_reformatted.csv(per-voxel grain IDs and orientations),reconstruction_cpfe_ee.csv(per-grain initial elastic strain),orientations.dat(per-grain Euler angles, always degrees), and the.tessfile. A GMSH tet.mshis written only whengenerate_mesh=True(a fallback; the default CPFE mesh is SCULPT hex).
Parameters that matter#
See Configuration reference for the full list.
bounding_boxandauto_fix_bbox/bbox_fix_mode: reconstruction domain and how out-of-box points are handled.weighted: Voronoi vs. Laguerre (size-weighted) tessellation.option(voronoi|centroid|centroidsize) andCVT_iter/morphoalgo: how seeds are relaxed to match measured centroids/sizes.tesr_size: voxel grid resolution of the reconstruction.rotate_angles/auto_rotateandunit: sample-frame alignment;unitmust match the data.
Further details#
For the full tessellation and CVT-optimization algorithm, see the NEPER documentation: https://neper.info/doc/ .
See also#
Tutorial: FF Voronoi reconstruction
API:
VoronoiMeshBuilder