upxo.pxtal.fm_steel_3d.with_pags_3d module

FMSteel3DWithPAGs: State class representing grain structure partitioned into PAGs.

This module contains the class for FM steel structures after PAG clustering. It adds hierarchical structure (PAGs and their contained grains) to the base grain structure.

Classes:

FMSteel3DWithPAGs: Base + PAGs state class.

class upxo.pxtal.fm_steel_3d.with_pags_3d.FMSteel3DWithPAGs(parent, clusters_dict: Dict[int, List[int]], neigh_clid: Dict[int, List[int]], pag_orientations: Dict[int, Tuple[float, float, float]] | None = None, isolated_grains: set | None = None, retained_austenite_pag_ids: set | None = None, target_pag_grain_fraction: float | None = None, retained_austenite_selection_info: Dict | None = None, random_seed: int | None = None, verbosity: int | None = None, log_sink=None)[source]

Bases: object

FM steel grain structure with PAG (Prior Austenite Grain) hierarchy.

Holds the base grain structure plus the computed PAG clustering. PAGs are groups of grains that will be further subdivided into packets and then blocks.

This is an intermediate state in the pipeline. It is created by calling generate_pag_clusters() on a FMSteel3DBase instance, and transitions to FMSteel3DWithBlocks by calling generate_blocks().

_parent

Reference to parent grain structure (read-only base state).

Type:

FMSteel3DBase

clusters_dict

Maps PAG ID → list of grain IDs contained in that PAG.

Type:

dict[int, list[int]]

neigh_clid

Maps PAG ID → list of neighboring PAG IDs.

Type:

dict[int, list[int]]

pag_orientations

Will store parent FCC (austenite) orientations for each PAG. Filled by assign_pag_orientations() or by downstream methods. Format: {pag_id: (phi1_deg, Phi_deg, phi2_deg)}.

Type:

dict[int, tuple[float, float, float]]

isolated_grains

Grain IDs that do NOT participate in PAG clustering (controlled by pag_grain_fraction parameter). Kept for backward compatibility – for PAGs produced via pag_technique_selector_3d. generate_pags(), this is now just the flattened grain membership of retained_austenite_pag_ids; the PAGs themselves are NOT removed from clusters_dict (see retained_austenite_pag_ids).

Type:

set[int]

retained_austenite_pag_ids

PAG IDs (present in clusters_dict) that were selected to remain untransformed retained austenite rather than proceed to block generation. Populated by pag_technique_selector_3d.generate_pags(); empty for PAGs produced via the legacy FMSteel3DBase. generate_pag_clusters() pre-filter path (which excludes grains before clustering ever assigns them a PAG identity to preserve). A retained-austenite PAG still gets an orientation from assign_pag_orientations() like any other PAG; generate_blocks() skips these PAG IDs when slicing blocks.

Type:

set[int]

isolated_grain_orientations

FCC Bunge Euler orientations (degrees) for each isolated grain. Empty until assign_isolated_grain_orientations() is called. Format: {grain_id: (phi1_deg, Phi_deg, phi2_deg)}. Only meaningful for the legacy flat-isolated-grain case (grains in isolated_grains but not covered by retained_austenite_pag_ids); grains covered by a tracked retained-austenite PAG already have a correct, group-consistent orientation via pag_orientations and should not be re-assigned here.

Type:

dict[int, tuple[float, float, float]]

grain_to_pag_id

Reverse lookup: grain_id -> pag_id. Derived from clusters_dict at construction time.

Type:

dict[int, int]

grain_to_local_pkt_idx

Maps grain_id -> 1-based ordinal of that grain within its PAG’s grain list (i.e. its local packet index). Ordinal follows the insertion order of clusters_dict[pag_id], which is determined by the clustering algorithm and is stable across the pipeline. Used to construct canonical elset names es.pck.{pag}.{pkt}.

Type:

dict[int, int]

_random_seed

Random seed used for this PAG generation.

Type:

int

clusters_dict
neigh_clid
isolated_grains
retained_austenite_pag_ids
pag_orientations
isolated_grain_orientations: Dict[int, Tuple[float, float, float]]
grain_to_pag_id: Dict[int, int]
grain_to_local_pkt_idx: Dict[int, int]
property lgi: numpy.ndarray

Labeled grain image from parent.

property grain_locs: Dict[int, numpy.ndarray]

Grain voxel coordinates from parent.

property neigh_gid: Dict[int, List[int]]

Grain neighbor relationships from parent.

property n_grains: int

Total grains in parent structure.

property physical_dimensions

Physical domain size from parent.

property voxel_size: float

Voxel size from parent.

property units: str

Physical unit string (‘microns’, ‘mm’, ‘m’) from base grain structure.

get_pag_phase(pag_id: int) → int[source]

Phase id (see phases_3d.py) for a given PAG.

Returns PHASE_RETAINED_AUSTENITE if pag_id is in retained_austenite_pag_ids, else PHASE_MARTENSITE. Does not validate that pag_id actually exists in clusters_dict.

property n_retained_austenite_pags: int

Number of PAGs tagged as retained austenite (still counted in n_pags/clusters_dict – these just never proceed to block generation).

property n_transformed_pags: int

Number of PAGs that will proceed to block generation (i.e. not retained austenite).

assign_pag_orientations(pag_ori_mode: str = 'random', pag_ori_params: Dict | None = None, random_seed: int | None = None) → None[source]

Assign FCC Bunge Euler orientations to PAGs in-place.

Parameters:
  • pag_ori_mode (str) – 'random' — uniform SO(3) (Phi sampled via arccos so the area element is correct). 'hagb_constrained' — random SO(3) with minimum HAGB constraint between neighbouring PAGs; pass hagb_threshold (float, deg), max_attempts (int), and optionally orientation_pool (list of (phi1,Phi,phi2) tuples) in pag_ori_params. 'textured' — sample PAG orientations from a synthetic FCC texture defined by named texture components; pass tc_info (dict of component → [volume_fraction, [phi1,Phi,phi2]]) and optionally N (pool size, default 5000) and hagb_constraint (bool, default False; if True, the texture pool is passed to the HAGB-constrained assigner). 'fixed' — all PAGs receive the same orientation; pass euler_angles=(phi1,Phi,phi2) in pag_ori_params.

  • pag_ori_params (dict, optional) – Extra parameters for the chosen mode (see above).

  • random_seed (int, optional) – RNG seed for reproducibility.

Returns:

Results stored in self.pag_orientations.

Return type:

None

generate_blocks(block_thickness_range: Tuple[float, float] = (2.0, 5.0), random_seed: int | None = None, block_slab_connectivity: int = 26) → FMSteel3DWithBlocks[source]

Slice each PAG into martensitic blocks.

PAG orientations must be assigned before calling this method (via assign_pag_orientations). If self.pag_orientations is empty a RuntimeWarning is issued and random SO(3) orientations are used as a fallback so the pipeline can still complete.

Parameters:
  • block_thickness_range (tuple of (float, float), optional) – (lower, upper) bounds for block thickness in physical units. Each packet independently draws its thickness uniformly from this range. Default (2.0, 5.0).

  • random_seed (int, optional) – Random seed for block slicing.

  • block_slab_connectivity (int, optional) – CC3D connectivity for within-slab connected-component labelling (6, 18, or 26). Default 26.

Returns:

New instance with computed block hierarchy.

Return type:

FMSteel3DWithBlocks

Notes

Does not modify self; returns a new instance.

assign_isolated_grain_orientations(mode: str = 'random', hagb_threshold: float = 15.0, max_attempts: int = 1000, orientation_pool: List[Tuple[float, float, float]] | None = None, random_seed: int | None = None) → None[source]

Assign FCC Bunge Euler orientations to isolated (unclustered) grains.

Isolated grains are those excluded from PAG clustering (see isolated_grains attribute). Because they were never part of a PAG they carry no crystallographic orientation after generate_blocks; this method assigns one, ensuring HAGB compatibility with all already- assigned neighbours (both PAGs and other isolated grains).

Only processes grains in isolated_grains that are NOT covered by a tracked retained_austenite_pag_ids entry – those already have a correct, group-consistent orientation from assign_pag_orientations (every grain in a retained-austenite PAG shares that PAG’s single orientation, the same as a transformed PAG’s grains do) and must not be overwritten with a separately-drawn one here. This method exists for the legacy case: grains excluded before clustering ever assigned them a PAG identity (FMSteel3DBase.generate_pag_clusters’s pre-filter path), which have no orientation to inherit at all.

Parameters:
  • mode (str) – 'random' — draw candidate orientations from uniform SO(3). 'textured' — draw candidates from orientation_pool (which must be supplied; typically built from FCCTexture.generate_euler).

  • hagb_threshold (float) – Minimum cubic disorientation (degrees) required between this grain and each already-assigned neighbour. Default 15.0°.

  • max_attempts (int) – Maximum candidate draws per grain. If the threshold is never met, the best-seen orientation (largest minimum misorientation with neighbours) is used and a RuntimeWarning is issued. Default 1000.

  • orientation_pool (list of (phi1, Phi, phi2), optional) – Pre-generated pool of Bunge Euler angles (degrees) for textured mode. Ignored when mode=’random’. Must be non-empty.

  • random_seed (int or None) – RNG seed for reproducibility.

Returns:

Results stored in self.isolated_grain_orientations.

Return type:

None

Raises:

ValueError – If mode=’textured’ and orientation_pool is None or empty.

ensure_isolated_grain_orientations(random_seed: int | None = None) → None[source]

Guarantee every isolated grain has a resolvable crystallographic orientation – non-negotiable: no feature other than voids may go unassigned.

Most isolated grains already satisfy this automatically: a retained-austenite PAG produced by generate_pags() gets its orientation the same way any PAG does, via assign_pag_orientations() (see FMSteel3DWithPAGs class docstring). The only grains that can ever be missing one are legacy leftover isolated grains (from FMSteel3DBase.generate_pag_clusters()’s pre-filter path, which never gave them a PAG identity to inherit from) that nobody has yet run assign_isolated_grain_orientations() for.

This method closes that gap automatically: if any isolated grain is found with no orientation available from either source, it auto-assigns via assign_isolated_grain_orientations(‘random’, …) for the uncovered ones, with a warning, rather than silently leaving a real microstructural feature unoriented. Idempotent and cheap to call repeatedly – a no-op once every isolated grain is covered, and never re-draws an orientation that already exists.

Called automatically by MeshExporter3D and by the phase-aware IPF query before either one reads isolated_grain_orientations, so this rarely needs to be called directly.

get_isolated_grain_orientation(gid: int) → Tuple[float, float, float] | None[source]

Resolve one isolated grain’s FCC orientation from whichever source actually has it: its retained-austenite PAG’s orientation (grain_to_pag_id -> pag_orientations) if tracked, else isolated_grain_orientations for the legacy leftover case. Returns None if neither source has an entry (call ensure_isolated_grain_orientations() first to guarantee that never happens).

get_pag_statistics(histogram_cap: int = 10, compute_morphology: bool = False) → Dict[source]

Compute statistics on PAG structure.

Parameters:
  • histogram_cap (int, optional) – Grain-count histogram is broken out per-count from 1 up to and including this value; any PAG with more grains than this is bucketed into a single “{cap+1}+” key. Default 10.

  • compute_morphology (bool, optional) – Also compute per-PAG aspect ratio and solidity (a convex hull per PAG – not free, so opt-in). Default False.

Notes

Histogram keys are strings (e.g. “1”, “2”, …, “11+”), not ints — this dict is persisted verbatim into shared_state/session JSON, and JSON object keys are always strings; using string keys natively avoids an int-vs-str mismatch after a save/reload round trip.

property n_pags: int

Number of PAGs.