Configuration reference#
This page collects the configuration objects used across graintrace: meshing, boundary conditions, segmentation, clustering weights, the CPFE parameter groups, and external-tool paths.
sculpt_config#
Required for any builder step that calls builder.mesh(sculpt_config=...).
It takes executable paths only, no license tokens.
sculpt_config = {
"launcher": "/path/to/cubit/bin/mpi/bin/mpiexec",
"psculpt": "/path/to/cubit/bin/psculpt",
"epu": "/path/to/cubit/bin/epu",
"nprocs": 10,
"environment": {
"OPAL_LIBDIR": "/path/to/cubit/bin/mpi/lib",
"OPAL_PREFIX": "/path/to/cubit/bin/mpi",
},
}
Required keys: psculpt, epu, nprocs. launcher and
environment are needed for MPI-based execution.
sculpt_options#
A tuple of CLI flag strings passed to SCULPT:
sculpt_options = (
"--adapt", "-A", "7", # mesh adaptation level
"-df", "1", # dilation factor
"-S", "2", # smoothing passes
"-CS", "4", # curve smoothing
"--void_mat", "0", # void material ID
)
For FF Voronoi meshes without adaptation, use just ("--void_mat", "0").
Boundary conditions (bc)#
bc = {
"x": {"negative": "stress_free", "positive": "stress_free"},
"y": {"negative": "stress_free", "positive": "stress_free"},
"z": {"negative": 0, "positive": displace_amount},
}
"stress_free" means traction-free (no constraint). An integer/float is a
prescribed displacement (0 = fixed).
Segmentation#
Two methods are available for voxel segmentation (EBSD / NF-as-voxel).
Flood fill (simpler and faster):
segmentation = {
"method": "flood",
"params": {
"misorientation_tol": 5.0 / 180 * np.pi, # radians for VoxelMeshBuilder
"connectivity": 26, # 6 or 26
"grain_threshold_final": 1000,
"batch_norm": 200_000,
"grain_threshold": 1000,
"stop_count": 500, # budget of discarded segments; must be >= 1
},
}
Graph-based (better for complex textures):
segmentation = {
"method": "graph",
"params": {
"misorientation_tol": 5.0, # degrees if angle_type="degrees", else radians
"connectivity": 26,
"grain_threshold_final": 100,
},
"graph_params": {
"segmenter": "leiden",
"graph_mode": "grid",
"manhattan_radius": 2,
"grid_tol": 1e-6,
"n_jobs": 10,
"weight_chunk_size": 1_000_000,
"reduce_edges_topweights_k": 8,
"nodes_chunk": 500_000,
"seed": 42,
"networkit_kwargs": {"gamma": 0.001}, # lower = fewer clusters
"weight_cfg": {
"mode": "rbf",
"sigma": None,
"sigma_auto": {"sample_size": 20_000, "random_state": 42, "quantile": 0.5},
"power": 2.0,
},
"plot": True,
},
}
For reconstruct(), the segmentation
argument is a flat dict (no method/params nesting), with
misorientation_tol in radians.
Graph/Leiden is the default and better pathway for NF/EBSD; flood over-merges
into percolating grains. The vetted recipe (validated on Fe-9Cr NF
reconstruction): hard 5° misorientation cutoff; manhattan_radius=2;
a fixed broad RBF sigma ≈ half the cutoff (sigma_auto collapses to ~0.1° and
shatters grains); reduce_edges_topweights_k=12; optional downsample=(2,2,1);
Leiden gamma chosen by percolation (smallest gamma whose biggest grain is
below ~3% of solid) over a sweep [0.5, 1, 2, 4, 8]; then absorb sub-30-voxel
fragments and an adjacency-only re-merge (~3°). This recipe is promoted into the
library as graintrace.fragmentation.FragmentationAnalyzer.segment() and its
staticmethods (gamma_sweep_leiden, pick_gamma_by_percolation,
absorb_fragments, adjacency_remerge). The percolation gamma-pick is for large, many-grain data;
for few-grain or intragranular sub-grain segmentation pass a fixed low gamma
(~0.1).
WeightConfig#
Controls edge weighting for graph clustering.
from graintrace.user_data_class import WeightConfig
weight_cfg = WeightConfig(
mode="rbf", # "rbf" | "inverse"
power=2.0,
sigma=None, # if None, use sigma_auto
sigma_auto={"sample_size": 500_000, "random_state": 42, "quantile": 0.5},
)
CPFE parameters#
set_parameters() takes named groups. The main
groups and common keys:
material: slip_constant_strength, voce_hardening_initial_slope,
voce_hardening_saturation, power_slip_n, power_slip_g0, elastic_E,
elastic_nu, elastic_G, burger_scale.
simulation_parameters: dt, total_time, initialize_time (load
ramps from initialize_time to total_time), device ("cpu",
"cuda:N", or a space-separated list), device_batch (per-device NEML2
chunk), sync_times (space-separated grid-output times). Output-frequency
knobs:
Knob |
Values |
|---|---|
|
|
|
|
|
|
|
|
|
|
The defaults are the cheap settings; the per-step grid transfer dominates wall time. Three sources of REI field data are described in Rare-event identification (REI).
The per-element mesh_out CSVs also carry a block_id column (the element
subdomain id == grain id). It lets post-processing select all elements of a
grain without the mesh file — e.g. intragranular reorientation /
fragmentation via
graintrace.ipf_orientation_tracking.OrientationTracker.simulation_element_tracks() (see
Reorientation and fragmentation).
Fragmentation & reorientation analysis#
Reorientation-driven REI and grain fragmentation (see Reorientation and fragmentation) add these options:
Reorientation REI
(graintrace.fragmentation.FragmentationAnalyzer.write_reorientation_field()): step and
ref_step (field-step indices; ref_step defaults to the first field step),
symmetry (default "432"), and extra_cols (feature columns to carry
through for a combined criterion). The rare-event scalar is compared with the
new abs_scalar_diff()
metric (exactly one feature column).
Orientation segmentation
(graintrace.fragmentation.FragmentationAnalyzer.segment()): misori_tol_deg
(hard edge cutoff, default 5°), graph_mode ("grid" for voxel grids,
"knn" for scattered mesh elements), gamma (fixed Leiden resolution; when
None a percolation-picked sweep over gamma_sweep runs), rbf_sigma_deg
(fixed broad RBF sigma, default ≈ half the cutoff — not sigma_auto, which
shatters grains), manhattan_radius, reduce_topk, grain_threshold_final
(absorb threshold), remerge_miso_deg (adjacency re-merge), and
percolation_max_frac. Graph/Leiden is the default/better pathway; flood
over-merges into percolating grains. The gamma-by-percolation sweep is for large,
many-grain NF/EBSD; for few-grain or intragranular sub-grain segmentation pass a
fixed low gamma (~0.25 for the shipped CPFE data — gamma=0.1 under-splits and
leaves most grains whole; raise gamma to resolve finer sub-grains).
For intragranular sub-grain segmentation,
grain_fragments() adds
min_subgrain_miso_deg (default 1°) — a minimum sub-grain misorientation applied after
Leiden: fragments whose symmetry-aware mean orientations are closer than this are merged
agglomeratively (closest-pair first, means recomputed each step, so no single-linkage chaining),
via merge_fragments_by_misorientation().
gamma sets the raw resolution; min_subgrain_miso_deg sets the physical threshold so a
smooth intragranular gradient is not chopped into near-identical fragments (0 disables it). The
fragment means (and the branching-IPF fork endpoints) use
mean_orientation_mrp(), a symmetry-aware
quaternion mean: pass symmetry (e.g. "432") so a set whose members are reported in
different symmetry variants still averages correctly (the default "1" is the plain mean).
Split detection
(graintrace.fragmentation.FragmentationAnalyzer.detect_splits()): d_tol
(cross-step centroid distance, at grain scale), theta_tol_deg (cross-step
misorientation link, generous ~15°), top_k, and an optional adjacency_b to
reject a spurious split child. adjacency_b only prunes a multi-child candidate
set that has a mutually adjacent pair; when no two candidates are adjacent there is
no split to reject, so the best candidate is kept and the parent still matches.
The same detector serves FF, NF, and EBSD; NF/EBSD
reach it through segment +
graintrace.fragmentation.FragmentationAnalyzer.seg_to_grain_table().
Annotated Exodus
(add_element_field_to_exodus() and
add_element_rgb_to_exodus()): write a
per-element (not block-constant) field into an Exodus copy, mapping values to elements
by a centroid KD-tree. The first writes a float scalar (encode the "{grain}.{k}"
fragment_label as a float); the second writes an RGB triple (rgb_x/rgb_y/rgb_z)
so ParaView can show the fragments in the same colors as the branching-IPF figure (map the
3-component array to color with scalar mapping off). Pass one color per element from the shared
fragment palette; for a consistent result annotate the Exodus of the analyzed mesh (a
co-registered loaded run’s sim_output.e/mesh.e), not a bare mesh of a different
microstructure.
IPF trajectory plot styling
(plot_ipf_orientation_tracking() and
plot_ipf_fragmentation_tracking()): both take a
shared ax (returning it instead of saving, for a side-by-side 1x2 figure) and the
styling knobs arrow_color (single color, a per-entity sequence, or the sentinel
"ipf_final" — tint each track/fork by the IPF color of its final-stage orientation,
which reads as “where it ended up” and needs no legend), start_color (start-dot color;
None = the initial-orientation IPF color, or e.g. "black" for a neutral origin),
line_style / child_line_styles + parent_line_style (per-track / per-child
linestyles), child_colors + parent_color (per-child colors and the parent color on the
fragmentation plot, so a grain’s sub-grains can be given contrasting categorical colors),
line_width, start_size, step_size, arrow_scale (arrowhead size), show_arrow
(False draws headless lines to declutter a crowded panel), and label_fontsize. The
signature 1x2 figure links its panels on two channels keyed to a per-grain fragment index: a
dark categorical color (a grain’s sub-grains contrast; grains told apart by IPF position) and a
shared fragment -> line-style map, with black start dots and a gray parent trajectory.
The solver_route selects one of two Executioner decks merged into the run.
"timestep_optimized" (default) uses a direct LU factorization
(superlu_dist) with preconditioner reuse — robust and iteration-cheap, but the
factorization fill-in makes it memory-bound on large meshes; best for small/medium
problems. "hpc_memory" uses an iterative fgmres solve preconditioned by
GAMG algebraic multigrid — no global factorization, so the memory footprint stays
low and scales across ranks; it is the recommended partner of distributed_mesh=True
for large HPC meshes, at the cost of looser and more numerous linear iterations. Both
decks keep residual_and_jacobian_together = false (required by the nodal-constraint
loading BC; MOOSE issue 33531).
Set distributed_mesh=True for large meshes that exhaust memory as a replicated
mesh: it pre-splits the mesh once (--split-mesh ncore) and runs --use-split
so each rank reads only its partition. It is pre-split only (no in-situ option),
requires ncore >= 2, and needs a puma-opt build with the
EqualValueBoundaryConstraint distributed-mesh fix. Outputs are unchanged (a
single sim_output.e via gather, plus complete mesh_out/ and grid_out/ CSVs).
Running on HPC#
For a cluster run, combine the memory-lean solver with the scheduler launcher, and add the distributed mesh only when the replicated mesh no longer fits:
sim.set_parameters(
"simulation_parameters",
solver_route="hpc_memory", # iterative fgmres + GAMG (low, scalable memory)
launcher="srun", # Slurm/Cray; default "mpiexec" elsewhere
distributed_mesh=True, # large meshes only (~1M+ elements); needs ncore >= 2
device="cuda:0 cuda:1 cuda:2 cuda:3", # GPU: a cuda list, one entry per GPU on the node
device_batch=20000, # finite chunk caps per-GPU memory (0 risks OOM)
)
sim.run(ncore=4) # GPU: ncore == number of GPUs in the device list
GPU node:
deviceis a space-separated cuda list andncoreequals the number of GPUs; fewer, larger ranks give better GPU utilisation for the NEML2-dominated solve. Keepdevice_batchfinite to bound per-GPU memory.CPU node: set
device="cpu"andncoreto the number of MPI ranks (srun -n);distributed_mesh=Trueis what keeps per-rank memory bounded as the mesh grows.solver_route="hpc_memory"anddistributed_mesh=Trueare independent levers but pair naturally: GAMG avoids the LU factorisation blow-up, the distributed mesh avoids holding the full mesh per rank. Enabledistributed_meshonly for meshes that OOM as replicated (it needs the EVBC-fixedpuma-opt);hpc_memoryis safe to use at any size.
boundary: bounding_box and the bc dict (above).
grid_properties: number_of_elements and bounding_box. The grid box
should be inset by a small amount on each face to avoid mesh-boundary issues:
grid_bb = bounding_box.copy()
for i in range(0, 6, 2): # xlo, ylo, zlo
grid_bb[i] += 0.0001
for i in range(1, 6, 2): # xhi, yhi, zhi
grid_bb[i] -= 0.0001
External-tool paths (tools.json)#
External tool locations can be supplied via a JSON file instead of environment
variables. The template is graintrace/mcp/tools.example.json with keys
puma_opt, neper, and a sculpt_config block. Search order:
$GRAINTRACE_TOOLS_JSON → ./graintrace_tools.json →
~/.config/graintrace/tools.json.